diff --git a/benchmark/benchmark_test.go b/benchmark/benchmark_test.go index a51476cd..fe3ce276 100644 --- a/benchmark/benchmark_test.go +++ b/benchmark/benchmark_test.go @@ -321,7 +321,6 @@ type benchmarkDoc struct { Key1 []int64 Key2 []string Key3 [][]int64 - // TODO: Key4 not supported by go-toml's Unmarshal Key4 []interface{} Key5 []int64 Key6 []int64 diff --git a/internal/danger/danger.go b/internal/danger/danger.go index e38e1131..e6a633c4 100644 --- a/internal/danger/danger.go +++ b/internal/danger/danger.go @@ -63,3 +63,14 @@ func Stride(ptr unsafe.Pointer, size uintptr, offset int) unsafe.Pointer { // https://github.com/golang/go/issues/40481 return unsafe.Pointer(uintptr(ptr) + uintptr(int(size)*offset)) } + +type Slice struct { + Data unsafe.Pointer + Len int + Cap int +} + +type iface struct { + typ unsafe.Pointer + ptr unsafe.Pointer +} diff --git a/internal/danger/slices.go b/internal/danger/slices.go new file mode 100644 index 00000000..8bce8eff --- /dev/null +++ b/internal/danger/slices.go @@ -0,0 +1,20 @@ +//go:build go1.18 +// +build go1.18 + +package danger + +import ( + "reflect" + "unsafe" +) + +func ExtendSlice(t reflect.Type, s *Slice, n int) Slice { + arrayType := reflect.ArrayOf(n, t.Elem()) + arrayData := reflect.New(arrayType) + reflect.Copy(arrayData.Elem(), reflect.NewAt(t, unsafe.Pointer(s)).Elem()) + return Slice{ + Data: unsafe.Pointer(arrayData.Pointer()), + Len: s.Len, + Cap: n, + } +} diff --git a/internal/danger/slices_optimized.go b/internal/danger/slices_optimized.go new file mode 100644 index 00000000..3d796853 --- /dev/null +++ b/internal/danger/slices_optimized.go @@ -0,0 +1,30 @@ +//go:build !go1.18 +// +build !go1.18 + +package danger + +import ( + "reflect" + "unsafe" +) + +//go:linkname unsafe_NewArray reflect.unsafe_NewArray +func unsafe_NewArray(rtype unsafe.Pointer, length int) unsafe.Pointer + +//go:linkname typedslicecopy reflect.typedslicecopy +//go:noescape +func typedslicecopy(elemType unsafe.Pointer, dst, src Slice) int + +func ExtendSlice(t reflect.Type, s *Slice, n int) Slice { + elemTypeRef := t.Elem() + elemTypePtr := ((*iface)(unsafe.Pointer(&elemTypeRef))).ptr + + d := Slice{ + Data: unsafe_NewArray(elemTypePtr, n), + Len: s.Len, + Cap: n, + } + + typedslicecopy(elemTypePtr, d, *s) + return d +} diff --git a/unmarshaler.go b/unmarshaler.go index 722e47d0..9c7ef526 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -11,6 +11,7 @@ import ( "strings" "sync/atomic" "time" + "unsafe" "github.com/pelletier/go-toml/v2/internal/ast" "github.com/pelletier/go-toml/v2/internal/danger" @@ -620,62 +621,128 @@ func (d *decoder) handleValue(value *ast.Node, v reflect.Value) error { } } -func (d *decoder) unmarshalArray(array *ast.Node, v reflect.Value) error { - switch v.Kind() { - case reflect.Slice: - if v.IsNil() { - v.Set(reflect.MakeSlice(v.Type(), 0, 16)) - } else { - v.SetLen(0) - } - case reflect.Array: - // arrays are always initialized - case reflect.Interface: - elem := v.Elem() - if !elem.IsValid() { - elem = reflect.New(sliceInterfaceType).Elem() - elem.Set(reflect.MakeSlice(sliceInterfaceType, 0, 16)) - } else if elem.Kind() == reflect.Slice { - if elem.Type() != sliceInterfaceType { - elem = reflect.New(sliceInterfaceType).Elem() - elem.Set(reflect.MakeSlice(sliceInterfaceType, 0, 16)) - } else if !elem.CanSet() { - nelem := reflect.New(sliceInterfaceType).Elem() - nelem.Set(reflect.MakeSlice(sliceInterfaceType, elem.Len(), elem.Cap())) - reflect.Copy(nelem, elem) - elem = nelem +type unmarshalArrayFn func(d *decoder, array *ast.Node, v reflect.Value) error + +var globalUnmarshalArrayFnCache atomic.Value // map[danger.TypeID]unmarshalArrayFn + +func unmarshalArrayFnForSlice(vt reflect.Type) unmarshalArrayFn { + tid := danger.MakeTypeID(vt) + + cache, _ := globalUnmarshalArrayFnCache.Load().(map[danger.TypeID]unmarshalArrayFn) + fn, ok := cache[tid] + + if ok { + return fn + } + + elemType := vt.Elem() + elemSize := elemType.Size() + + fn = func(d *decoder, array *ast.Node, v reflect.Value) error { + sp := (*danger.Slice)(unsafe.Pointer(v.UnsafeAddr())) + + sp.Len = 0 + + it := array.Children() + for it.Next() { + n := it.Node() + + idx := sp.Len + + if sp.Len == sp.Cap { + c := sp.Cap + if c == 0 { + c = 16 + } else { + c *= 2 + } + *sp = danger.ExtendSlice(vt, sp, c) } + + datap := unsafe.Pointer(sp.Data) + elemp := danger.Stride(datap, elemSize, idx) + elem := reflect.NewAt(elemType, elemp).Elem() + + err := d.handleValue(n, elem) + if err != nil { + return err + } + + sp.Len++ } - err := d.unmarshalArray(array, elem) - if err != nil { - return err + + if sp.Data == nil { + *sp = danger.ExtendSlice(vt, sp, 0) } - v.Set(elem) + return nil - default: - // TODO: use newDecodeError, but first the parser needs to fill - // array.Data. - return fmt.Errorf("toml: cannot store array in Go type %s", v.Kind()) } - elemType := v.Type().Elem() + newCache := make(map[danger.TypeID]unmarshalArrayFn, len(cache)+1) + newCache[tid] = fn + for k, v := range cache { + newCache[k] = v + } + globalUnmarshalArrayFnCache.Store(newCache) + + return fn +} + +func unmarshalArraySliceInterface(d *decoder, array *ast.Node, v reflect.Value) error { + sp := (*danger.Slice)(unsafe.Pointer(v.UnsafeAddr())) + + sp.Len = 0 + + var x interface{} it := array.Children() - idx := 0 for it.Next() { n := it.Node() - // TODO: optimize - if v.Kind() == reflect.Slice { - elem := reflect.New(elemType).Elem() + idx := sp.Len - err := d.handleValue(n, elem) - if err != nil { - return err + if sp.Len == sp.Cap { + c := sp.Cap + if c == 0 { + c = 16 + } else { + c *= 2 } + *sp = danger.ExtendSlice(sliceInterfaceType, sp, c) + } + + datap := unsafe.Pointer(sp.Data) + elemp := danger.Stride(datap, unsafe.Sizeof(x), idx) + elem := reflect.NewAt(sliceInterfaceType.Elem(), elemp).Elem() + + err := d.handleValue(n, elem) + if err != nil { + return err + } + + sp.Len++ + } + + if sp.Data == nil { + *sp = danger.ExtendSlice(sliceInterfaceType, sp, 0) + } + + return nil +} + +func (d *decoder) unmarshalArray(array *ast.Node, v reflect.Value) error { + switch v.Kind() { + case reflect.Slice: + fn := unmarshalArrayFnForSlice(v.Type()) + return fn(d, array, v) + case reflect.Array: + // arrays are always initialized + + it := array.Children() + idx := 0 + for it.Next() { + n := it.Node() - v.Set(reflect.Append(v, elem)) - } else { // array if idx >= v.Len() { return nil } @@ -686,6 +753,39 @@ func (d *decoder) unmarshalArray(array *ast.Node, v reflect.Value) error { } idx++ } + case reflect.Interface: + elemIsSliceInterface := false + elem := v.Elem() + if !elem.IsValid() { + s := make([]interface{}, 0, 16) + elem = reflect.ValueOf(&s).Elem() + elemIsSliceInterface = true + } else if elem.Kind() == reflect.Slice { + if elem.Type() != sliceInterfaceType { + s := make([]interface{}, 0, 16) + elem = reflect.ValueOf(&s).Elem() + } else if !elem.CanSet() { + s := make([]interface{}, elem.Len(), elem.Cap()) + nelem := reflect.ValueOf(&s).Elem() + reflect.Copy(nelem, elem) + elem = nelem + } + elemIsSliceInterface = true + } + + var err error + if elemIsSliceInterface { + err = unmarshalArraySliceInterface(d, array, elem) + } else { + err = d.unmarshalArray(array, elem) + } + + v.Set(elem) + return err + default: + // TODO: use newDecodeError, but first the parser needs to fill + // array.Data. + return fmt.Errorf("toml: cannot store array in Go type %s", v.Kind()) } return nil @@ -1078,9 +1178,10 @@ var globalFieldPathsCache atomic.Value // map[danger.TypeID]fieldPathsMap func structField(v reflect.Value, name string) (reflect.Value, bool) { t := v.Type() + tid := danger.MakeTypeID(t) cache, _ := globalFieldPathsCache.Load().(map[danger.TypeID]fieldPathsMap) - fieldPaths, ok := cache[danger.MakeTypeID(t)] + fieldPaths, ok := cache[tid] if !ok { fieldPaths = map[string][]int{} @@ -1092,7 +1193,7 @@ func structField(v reflect.Value, name string) (reflect.Value, bool) { }) newCache := make(map[danger.TypeID]fieldPathsMap, len(cache)+1) - newCache[danger.MakeTypeID(t)] = fieldPaths + newCache[tid] = fieldPaths for k, v := range cache { newCache[k] = v }