Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Decode: slices specialization #669

Draft
wants to merge 6 commits into
base: v2
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 0 additions & 1 deletion benchmark/benchmark_test.go
Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions internal/danger/danger.go
Expand Up @@ -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
}
20 changes: 20 additions & 0 deletions 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,
}
}
30 changes: 30 additions & 0 deletions 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
}
189 changes: 145 additions & 44 deletions unmarshaler.go
Expand Up @@ -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"
Expand Down Expand Up @@ -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
}
Expand All @@ -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
Expand Down Expand Up @@ -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{}
Expand All @@ -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
}
Expand Down