Browse Source

codec: streamline unsafe (and safe) map range implementation

Now, unsafe map range is optimized for all versions of go 1.7+

We leverage some unsafe commonality that is not limited to
what is exposed in reflect package.

go 1.4+ support hashiter with the first 2 elements being
key and value, so we depend on that.

Furthermore, for safe mode, we just delegate directly to the standard
library methods and do not "cache" the results.
We know that the mapAddressableRV for safe mode is always a zero value.
Ugorji Nwoke 6 years ago
parent
commit
f5cdbb5f50

+ 7 - 40
codec/goversion_maprange_gte_go112.go

@@ -9,63 +9,30 @@ package codec
 import "reflect"
 import "reflect"
 
 
 type mapIter struct {
 type mapIter struct {
-	t        *reflect.MapIter
-	m, k, v  reflect.Value
-	kOk, vOk bool
-	values   bool
+	t      *reflect.MapIter
+	m      reflect.Value
+	values bool
 }
 }
 
 
 func (t *mapIter) Next() (r bool) {
 func (t *mapIter) Next() (r bool) {
-	r = t.t.Next()
-	if r {
-		if t.kOk {
-			t.k.Set(t.t.Key())
-		}
-		if t.vOk {
-			t.v.Set(t.t.Value())
-		}
-	}
-	return
+	return t.t.Next()
 }
 }
 
 
 func (t *mapIter) Key() reflect.Value {
 func (t *mapIter) Key() reflect.Value {
-	if t.kOk {
-		return t.k
-	}
 	return t.t.Key()
 	return t.t.Key()
 }
 }
 
 
 func (t *mapIter) Value() (r reflect.Value) {
 func (t *mapIter) Value() (r reflect.Value) {
-	if !t.values {
-		return
+	if t.values {
+		return t.t.Value()
 	}
 	}
-	if t.vOk {
-		return t.v
-	}
-	return t.t.Value()
+	return
 }
 }
 
 
 func mapRange(m, k, v reflect.Value, values bool) *mapIter {
 func mapRange(m, k, v reflect.Value, values bool) *mapIter {
 	return &mapIter{
 	return &mapIter{
 		m:      m,
 		m:      m,
-		k:      k,
-		v:      v,
-		kOk:    k.CanSet(),
-		vOk:    values && v.CanSet(),
 		t:      m.MapRange(),
 		t:      m.MapRange(),
 		values: values,
 		values: values,
 	}
 	}
 }
 }
-
-func mapIndex(m, k, v reflect.Value) (vv reflect.Value) {
-	vv = m.MapIndex(k)
-	if vv.IsValid() && v.CanSet() {
-		v.Set(vv)
-	}
-	return
-}
-
-// return an addressable reflect value that can be used in mapRange and mapIndex operations.
-func mapAddressableRV(t reflect.Type) (r reflect.Value) {
-	return // reflect.New(t).Elem()
-}

+ 9 - 40
codec/goversion_maprange_lt_go112.go

@@ -8,64 +8,33 @@ package codec
 import "reflect"
 import "reflect"
 
 
 type mapIter struct {
 type mapIter struct {
-	m, k, v  reflect.Value
-	keys     []reflect.Value
-	j        int
-	kOk, vOk bool
-	values   bool
+	m      reflect.Value
+	keys   []reflect.Value
+	j      int
+	values bool
 }
 }
 
 
 func (t *mapIter) Next() (r bool) {
 func (t *mapIter) Next() (r bool) {
 	t.j++
 	t.j++
-	r = t.j < len(t.keys)
-	if r {
-		if t.kOk {
-			t.k.Set(t.keys[t.j])
-		}
-		if t.vOk {
-			t.v.Set(t.m.MapIndex(t.keys[t.j]))
-		}
-	}
-	return
+	return t.j < len(t.keys)
 }
 }
 
 
 func (t *mapIter) Key() reflect.Value {
 func (t *mapIter) Key() reflect.Value {
-	if t.kOk {
-		return t.k
-	}
 	return t.keys[t.j]
 	return t.keys[t.j]
 }
 }
 
 
 func (t *mapIter) Value() (r reflect.Value) {
 func (t *mapIter) Value() (r reflect.Value) {
-	if !t.values {
-		return
+	if t.values {
+		return t.m.MapIndex(t.keys[t.j])
 	}
 	}
-	if t.vOk {
-		return t.v
-	}
-	return t.m.MapIndex(t.keys[t.j])
+	return
 }
 }
 
 
 func mapRange(m, k, v reflect.Value, values bool) *mapIter {
 func mapRange(m, k, v reflect.Value, values bool) *mapIter {
 	return &mapIter{
 	return &mapIter{
-		m: m, k: k, v: v,
-		kOk:    k.CanSet(),
-		vOk:    values && v.CanSet(),
+		m:      m,
 		keys:   m.MapKeys(),
 		keys:   m.MapKeys(),
 		values: values,
 		values: values,
 		j:      -1,
 		j:      -1,
 	}
 	}
 }
 }
-
-func mapIndex(m, k, v reflect.Value) (vv reflect.Value) {
-	vv = m.MapIndex(k)
-	if vv.IsValid() && v.CanSet() {
-		v.Set(vv)
-	}
-	return
-}
-
-// return an addressable reflect value that can be used in mapRange and mapIndex operations.
-func mapAddressableRV(t reflect.Type) (r reflect.Value) {
-	return // reflect.New(t).Elem()
-}

+ 0 - 15
codec/goversion_maprange_unsafe_eq_go112.go

@@ -1,15 +0,0 @@
-// Copyright (c) 2012-2018 Ugorji Nwoke. All rights reserved.
-// Use of this source code is governed by a MIT license found in the LICENSE file.
-
-// +build !safe
-// +build !appengine
-// +build go1.12
-// +build !go1.13
-
-package codec
-
-import "unsafe"
-
-//go:linkname mapiterelem reflect.mapitervalue
-//go:noescape
-func mapiterelem(it unsafe.Pointer) (elem unsafe.Pointer)

+ 0 - 131
codec/goversion_maprange_unsafe_gte_go112.go

@@ -1,131 +0,0 @@
-// Copyright (c) 2012-2018 Ugorji Nwoke. All rights reserved.
-// Use of this source code is governed by a MIT license found in the LICENSE file.
-
-// +build !safe
-// +build !appengine
-// +build go1.12
-
-package codec
-
-import (
-	"reflect"
-	"unsafe"
-)
-
-type unsafeReflectMapIter struct {
-	m  unsafeReflectValue
-	it unsafe.Pointer
-}
-
-type mapIter struct {
-	t              *reflect.MapIter
-	m, k, v        reflect.Value
-	ktyp, vtyp     unsafe.Pointer
-	kptr, vptr     unsafe.Pointer
-	kisref, visref bool
-	mapvalues      bool
-	done           bool
-	_              uint64 // padding (cache-aligned)
-}
-
-func (t *mapIter) Next() (r bool) {
-	if t.done {
-		return
-	}
-	r = t.t.Next()
-	if r {
-		it := (*unsafeReflectMapIter)(unsafe.Pointer(t.t))
-		mapSet(t.kptr, t.ktyp, mapiterkey(it.it), t.kisref)
-		if t.mapvalues {
-			mapSet(t.vptr, t.vtyp, mapiterelem(it.it), t.visref)
-		}
-	} else {
-		t.done = true
-	}
-	return
-}
-
-func (t *mapIter) Key() reflect.Value {
-	return t.k
-}
-
-func (t *mapIter) Value() (r reflect.Value) {
-	if t.mapvalues {
-		return t.v
-	}
-	return
-}
-
-func mapSet(p, ptyp, p2 unsafe.Pointer, isref bool) {
-	if isref {
-		*(*unsafe.Pointer)(p) = *(*unsafe.Pointer)(p2) // p2
-	} else {
-		typedmemmove(ptyp, p, p2) // *(*unsafe.Pointer)(p2)) // p2)
-	}
-}
-
-func mapRange(m, k, v reflect.Value, mapvalues bool) *mapIter {
-	if m.IsNil() {
-		return &mapIter{done: true}
-	}
-	t := &mapIter{
-		m: m, k: k, v: v,
-		t: m.MapRange(), mapvalues: mapvalues,
-	}
-
-	var urv *unsafeReflectValue
-	urv = (*unsafeReflectValue)(unsafe.Pointer(&k))
-	t.ktyp = urv.typ
-	t.kptr = urv.ptr
-	t.kisref = refBitset.isset(byte(k.Kind()))
-	if mapvalues {
-		urv = (*unsafeReflectValue)(unsafe.Pointer(&v))
-		t.vtyp = urv.typ
-		t.vptr = urv.ptr
-		t.visref = refBitset.isset(byte(v.Kind()))
-	}
-
-	return t
-}
-
-func mapIndex(m, k, v reflect.Value) (vv reflect.Value) {
-	var urv = (*unsafeReflectValue)(unsafe.Pointer(&k))
-	var kptr unsafe.Pointer
-	if urv.flag&unsafeFlagIndir == 0 {
-		kptr = unsafe.Pointer(&urv.ptr)
-	} else {
-		kptr = urv.ptr
-	}
-
-	urv = (*unsafeReflectValue)(unsafe.Pointer(&m))
-
-	vvptr := mapaccess(urv.typ, rv2ptr(urv), kptr)
-	if vvptr == nil {
-		return
-	}
-	// vvptr = *(*unsafe.Pointer)(vvptr)
-
-	urv = (*unsafeReflectValue)(unsafe.Pointer(&v))
-
-	mapSet(urv.ptr, urv.typ, vvptr, refBitset.isset(byte(v.Kind())))
-	return v
-}
-
-// return an addressable reflect value that can be used in mapRange and mapIndex operations.
-//
-// all calls to mapIndex or mapRange will call here to get an addressable reflect.Value.
-func mapAddressableRV(t reflect.Type) (r reflect.Value) {
-	return reflect.New(t).Elem()
-}
-
-//go:linkname mapiterkey reflect.mapiterkey
-//go:noescape
-func mapiterkey(it unsafe.Pointer) (key unsafe.Pointer)
-
-//go:linkname mapaccess reflect.mapaccess
-//go:noescape
-func mapaccess(rtype unsafe.Pointer, m unsafe.Pointer, key unsafe.Pointer) (val unsafe.Pointer)
-
-//go:linkname typedmemmove reflect.typedmemmove
-//go:noescape
-func typedmemmove(typ unsafe.Pointer, dst, src unsafe.Pointer)

+ 0 - 14
codec/goversion_maprange_unsafe_gte_go113.go

@@ -1,14 +0,0 @@
-// Copyright (c) 2012-2018 Ugorji Nwoke. All rights reserved.
-// Use of this source code is governed by a MIT license found in the LICENSE file.
-
-// +build !safe
-// +build !appengine
-// +build go1.13
-
-package codec
-
-import "unsafe"
-
-//go:linkname mapiterelem reflect.mapiterelem
-//go:noescape
-func mapiterelem(it unsafe.Pointer) (elem unsafe.Pointer)

+ 17 - 0
codec/helper_not_unsafe.go

@@ -304,6 +304,23 @@ func (e *Encoder) kUintptr(f *codecFnInfo, rv reflect.Value) {
 	e.e.EncodeUint(rv.Uint())
 	e.e.EncodeUint(rv.Uint())
 }
 }
 
 
+// ------------ map range and map indexing ----------
+
+func mapIndex(m, k, v reflect.Value) (vv reflect.Value) {
+	return m.MapIndex(k)
+	// if vv.IsValid() && v.CanSet() {
+	// 	v.Set(vv)
+	// }
+	// return
+}
+
+// return an addressable reflect value that can be used in mapRange and mapIndex operations.
+//
+// all calls to mapIndex or mapRange will call here to get an addressable reflect.Value.
+func mapAddressableRV(t reflect.Type) (r reflect.Value) {
+	return // reflect.New(t).Elem()
+}
+
 // // keepAlive4BytesView maintains a reference to the input parameter for bytesView.
 // // keepAlive4BytesView maintains a reference to the input parameter for bytesView.
 // //
 // //
 // // Usage: call this at point where done with the bytes view.
 // // Usage: call this at point where done with the bytes view.

+ 148 - 1
codec/helper_unsafe.go

@@ -487,7 +487,154 @@ func (e *Encoder) kUintptr(f *codecFnInfo, rv reflect.Value) {
 	e.e.EncodeUint(uint64(*(*uintptr)(v.ptr)))
 	e.e.EncodeUint(uint64(*(*uintptr)(v.ptr)))
 }
 }
 
 
-// ------------
+// ------------ map range and map indexing ----------
+
+// unsafeMapHashIter
+//
+// go 1.4+ has runtime/hashmap.go or runtime/map.go which has a
+// hIter struct with the first 2 values being key and value
+// of the current iteration.
+//
+// This *hIter is passed to mapiterinit, mapiternext, mapiterkey, mapiterelem.
+// We bypass the reflect wrapper functions and just use the *hIter directly.
+//
+// Though *hIter has many fields, we only care about the first 2.
+type unsafeMapHashIter struct {
+	key, value unsafe.Pointer
+	// other fields are ignored
+}
+
+// type unsafeReflectMapIter struct {
+// 	m  unsafeReflectValue
+// 	it unsafe.Pointer
+// }
+
+type unsafeMapIter struct {
+	it               *unsafeMapHashIter
+	k, v             reflect.Value
+	mtyp, ktyp, vtyp unsafe.Pointer
+	mptr, kptr, vptr unsafe.Pointer
+	kisref, visref   bool
+	mapvalues        bool
+	done             bool
+
+	// _ [2]uint64 // padding (cache-aligned)
+}
+
+func (t *unsafeMapIter) Next() (r bool) {
+	if t.done {
+		return
+	}
+	if t.it == nil {
+		t.it = (*unsafeMapHashIter)(mapiterinit(t.mtyp, t.mptr))
+	} else {
+		mapiternext((unsafe.Pointer)(t.it))
+	}
+	t.done = t.it.key == nil
+	if t.done {
+		return
+	}
+	unsafeMapSet(t.kptr, t.ktyp, t.it.key, t.kisref)
+	if t.mapvalues {
+		unsafeMapSet(t.vptr, t.vtyp, t.it.value, t.visref)
+	}
+	return true
+}
+
+func (t *unsafeMapIter) Key() reflect.Value {
+	return t.k
+}
+
+func (t *unsafeMapIter) Value() (r reflect.Value) {
+	if t.mapvalues {
+		return t.v
+	}
+	return
+}
+
+func unsafeMapSet(p, ptyp, p2 unsafe.Pointer, isref bool) {
+	if isref {
+		*(*unsafe.Pointer)(p) = *(*unsafe.Pointer)(p2) // p2
+	} else {
+		typedmemmove(ptyp, p, p2) // *(*unsafe.Pointer)(p2)) // p2)
+	}
+}
+
+func mapRange(m, k, v reflect.Value, mapvalues bool) *unsafeMapIter {
+	if m.IsNil() {
+		return &unsafeMapIter{done: true}
+	}
+	t := &unsafeMapIter{
+		k: k, v: v,
+		mapvalues: mapvalues,
+	}
+
+	var urv *unsafeReflectValue
+
+	urv = (*unsafeReflectValue)(unsafe.Pointer(&m))
+	t.mtyp = urv.typ
+	t.mptr = rv2ptr(urv)
+
+	urv = (*unsafeReflectValue)(unsafe.Pointer(&k))
+	t.ktyp = urv.typ
+	t.kptr = urv.ptr
+	t.kisref = refBitset.isset(byte(k.Kind()))
+
+	if mapvalues {
+		urv = (*unsafeReflectValue)(unsafe.Pointer(&v))
+		t.vtyp = urv.typ
+		t.vptr = urv.ptr
+		t.visref = refBitset.isset(byte(v.Kind()))
+	}
+
+	return t
+}
+
+func mapIndex(m, k, v reflect.Value) (vv reflect.Value) {
+	var urv = (*unsafeReflectValue)(unsafe.Pointer(&k))
+	var kptr unsafe.Pointer
+	if urv.flag&unsafeFlagIndir == 0 {
+		kptr = unsafe.Pointer(&urv.ptr)
+	} else {
+		kptr = urv.ptr
+	}
+
+	urv = (*unsafeReflectValue)(unsafe.Pointer(&m))
+
+	vvptr := mapaccess(urv.typ, rv2ptr(urv), kptr)
+	if vvptr == nil {
+		return
+	}
+	// vvptr = *(*unsafe.Pointer)(vvptr)
+
+	urv = (*unsafeReflectValue)(unsafe.Pointer(&v))
+
+	unsafeMapSet(urv.ptr, urv.typ, vvptr, refBitset.isset(byte(v.Kind())))
+	return v
+}
+
+// return an addressable reflect value that can be used in mapRange and mapIndex operations.
+//
+// all calls to mapIndex or mapRange will call here to get an addressable reflect.Value.
+func mapAddressableRV(t reflect.Type) (r reflect.Value) {
+	return reflect.New(t).Elem()
+}
+
+//go:linkname mapiterinit reflect.mapiterinit
+//go:noescape
+func mapiterinit(typ unsafe.Pointer, it unsafe.Pointer) (key unsafe.Pointer)
+
+//go:linkname mapiternext reflect.mapiternext
+//go:noescape
+func mapiternext(it unsafe.Pointer) (key unsafe.Pointer)
+
+//go:linkname mapaccess reflect.mapaccess
+//go:noescape
+func mapaccess(rtype unsafe.Pointer, m unsafe.Pointer, key unsafe.Pointer) (val unsafe.Pointer)
+
+//go:linkname typedmemmove reflect.typedmemmove
+//go:noescape
+func typedmemmove(typ unsafe.Pointer, dst, src unsafe.Pointer)
 
 
 // func (d *Decoder) raw(f *codecFnInfo, rv reflect.Value) {
 // func (d *Decoder) raw(f *codecFnInfo, rv reflect.Value) {
 // 	urv := (*unsafeReflectValue)(unsafe.Pointer(&rv))
 // 	urv := (*unsafeReflectValue)(unsafe.Pointer(&rv))