Browse Source

codec: support mapping non-interface type to an interface

This allows us decode appropriately into any interface type
with a correctly configured non-interface value.

Also did misc clean ups in preparation for v1.1 release
Ugorji Nwoke 8 years ago
parent
commit
d2b2650062
11 changed files with 245 additions and 102 deletions
  1. 11 5
      codec/0doc.go
  2. 8 5
      codec/README.md
  3. 1 1
      codec/binc.go
  4. 62 0
      codec/codec_test.go
  5. 15 15
      codec/decode.go
  6. 6 10
      codec/encode.go
  7. 54 5
      codec/helper.go
  8. 23 20
      codec/helper_not_unsafe.go
  9. 58 41
      codec/helper_unsafe.go
  10. 1 0
      codec/rpc.go
  11. 6 0
      codec/z_all_test.go

+ 11 - 5
codec/0doc.go

@@ -39,9 +39,8 @@ Rich Feature Set includes:
   - Careful selected use of 'unsafe' for targeted performance gains.
   - Careful selected use of 'unsafe' for targeted performance gains.
     100% mode exists where 'unsafe' is not used at all.
     100% mode exists where 'unsafe' is not used at all.
   - Lock-free (sans mutex) concurrency for scaling to 100's of cores
   - Lock-free (sans mutex) concurrency for scaling to 100's of cores
-  - Multiple conversions:
-    Package coerces types where appropriate
-    e.g. decode an int in the stream into a float, etc.
+  - Coerce types where appropriate
+    e.g. decode an int in the stream into a float, decode numbers from formatted strings, etc
   - Corner Cases:
   - Corner Cases:
     Overflows, nil maps/slices, nil values in streams are handled correctly
     Overflows, nil maps/slices, nil values in streams are handled correctly
   - Standard field renaming via tags
   - Standard field renaming via tags
@@ -50,9 +49,13 @@ Rich Feature Set includes:
     (struct, slice, map, primitives, pointers, interface{}, etc)
     (struct, slice, map, primitives, pointers, interface{}, etc)
   - Extensions to support efficient encoding/decoding of any named types
   - Extensions to support efficient encoding/decoding of any named types
   - Support encoding.(Binary|Text)(M|Unm)arshaler interfaces
   - Support encoding.(Binary|Text)(M|Unm)arshaler interfaces
+  - Support IsZero() bool to determine if a value is a zero value.
+    Analogous to time.Time.IsZero() bool.
   - Decoding without a schema (into a interface{}).
   - Decoding without a schema (into a interface{}).
     Includes Options to configure what specific map or slice type to use
     Includes Options to configure what specific map or slice type to use
     when decoding an encoded list or map into a nil interface{}
     when decoding an encoded list or map into a nil interface{}
+  - Mapping a non-interface type to an interface, so we can decode appropriately
+    into any interface type with a correctly configured non-interface value.
   - Encode a struct as an array, and decode struct from an array in the data stream
   - Encode a struct as an array, and decode struct from an array in the data stream
   - Option to encode struct keys as numbers (instead of strings)
   - Option to encode struct keys as numbers (instead of strings)
     (to support structured streams with fields encoded as numeric codes)
     (to support structured streams with fields encoded as numeric codes)
@@ -210,8 +213,8 @@ Struct fields matching the following are ignored during encoding and decoding
     - struct tag value set to -
     - struct tag value set to -
     - func, complex numbers, unsafe pointers
     - func, complex numbers, unsafe pointers
     - unexported and not embedded
     - unexported and not embedded
-    - unexported embedded non-struct
-    - unexported embedded pointers (from go1.10)
+    - unexported and embedded and not struct kind
+    - unexported and embedded pointers (from go1.10)
 
 
 Every other field in a struct will be encoded/decoded.
 Every other field in a struct will be encoded/decoded.
 
 
@@ -240,6 +243,8 @@ package codec
 //     and use overlay methods on *BasicHandle to call through to extHandle after initializing
 //     and use overlay methods on *BasicHandle to call through to extHandle after initializing
 //     the "xh *extHandle" to point to a real slice.
 //     the "xh *extHandle" to point to a real slice.
 //
 //
+//   - Allow mapping a concrete type to an interface, for use during decoding.
+//
 // BEFORE EACH RELEASE:
 // BEFORE EACH RELEASE:
 //   - Look through and fix padding for each type, to eliminate false sharing
 //   - Look through and fix padding for each type, to eliminate false sharing
 //     - critical shared objects that are read many times
 //     - critical shared objects that are read many times
@@ -261,3 +266,4 @@ package codec
 //     Check this out by running: ./run.sh -z
 //     Check this out by running: ./run.sh -z
 //     - look at those tagged ****, meaning they are not occupying full cache lines
 //     - look at those tagged ****, meaning they are not occupying full cache lines
 //     - look at those tagged <<<<, meaning they are larger than 32 words (something to watch)
 //     - look at those tagged <<<<, meaning they are larger than 32 words (something to watch)
+//   - Run "golint -min_confidence 0.81"

+ 8 - 5
codec/README.md

@@ -37,9 +37,8 @@ Rich Feature Set includes:
   - Careful selected use of 'unsafe' for targeted performance gains.
   - Careful selected use of 'unsafe' for targeted performance gains.
     100% mode exists where 'unsafe' is not used at all.
     100% mode exists where 'unsafe' is not used at all.
   - Lock-free (sans mutex) concurrency for scaling to 100's of cores
   - Lock-free (sans mutex) concurrency for scaling to 100's of cores
-  - Multiple conversions:
-    Package coerces types where appropriate 
-    e.g. decode an int in the stream into a float, etc.
+  - Coerce types where appropriate
+    e.g. decode an int in the stream into a float, decode numbers from formatted strings, etc
   - Corner Cases: 
   - Corner Cases: 
     Overflows, nil maps/slices, nil values in streams are handled correctly
     Overflows, nil maps/slices, nil values in streams are handled correctly
   - Standard field renaming via tags
   - Standard field renaming via tags
@@ -48,9 +47,13 @@ Rich Feature Set includes:
     (struct, slice, map, primitives, pointers, interface{}, etc)
     (struct, slice, map, primitives, pointers, interface{}, etc)
   - Extensions to support efficient encoding/decoding of any named types
   - Extensions to support efficient encoding/decoding of any named types
   - Support encoding.(Binary|Text)(M|Unm)arshaler interfaces
   - Support encoding.(Binary|Text)(M|Unm)arshaler interfaces
+  - Support IsZero() bool to determine if a value is a zero value.
+    Analogous to time.Time.IsZero() bool.
   - Decoding without a schema (into a interface{}).
   - Decoding without a schema (into a interface{}).
     Includes Options to configure what specific map or slice type to use
     Includes Options to configure what specific map or slice type to use
     when decoding an encoded list or map into a nil interface{}
     when decoding an encoded list or map into a nil interface{}
+  - Mapping a non-interface type to an interface, so we can decode appropriately
+    into any interface type with a correctly configured non-interface value.
   - Encode a struct as an array, and decode struct from an array in the data stream
   - Encode a struct as an array, and decode struct from an array in the data stream
   - Option to encode struct keys as numbers (instead of strings)
   - Option to encode struct keys as numbers (instead of strings)
     (to support structured streams with fields encoded as numeric codes)
     (to support structured streams with fields encoded as numeric codes)
@@ -194,8 +197,8 @@ Struct fields matching the following are ignored during encoding and decoding
   - struct tag value set to -
   - struct tag value set to -
   - func, complex numbers, unsafe pointers
   - func, complex numbers, unsafe pointers
   - unexported and not embedded
   - unexported and not embedded
-  - unexported embedded non-struct
-  - unexported embedded pointers (from go1.10)
+  - unexported and embedded and not struct kind
+  - unexported and embedded pointers (from go1.10)
 
 
 Every other field in a struct will be encoded/decoded.
 Every other field in a struct will be encoded/decoded.
 
 

+ 1 - 1
codec/binc.go

@@ -918,7 +918,7 @@ type BincHandle struct {
 	// - 0: default: library uses best judgement
 	// - 0: default: library uses best judgement
 	// - 1: use symbols
 	// - 1: use symbols
 	// - 2: do not use symbols
 	// - 2: do not use symbols
-	AsSymbols byte
+	AsSymbols uint8
 
 
 	// AsSymbols: may later on introduce more options ...
 	// AsSymbols: may later on introduce more options ...
 	// - m: map keys
 	// - m: map keys

+ 62 - 0
codec/codec_test.go

@@ -46,6 +46,24 @@ type testMbsCustStrT []testCustomStringT
 
 
 func (testMbsCustStrT) MapBySlice() {}
 func (testMbsCustStrT) MapBySlice() {}
 
 
+type testIntfMapI interface {
+	GetIntfMapV() string
+}
+
+type testIntfMapT1 struct {
+	IntfMapV string
+}
+
+func (x *testIntfMapT1) GetIntfMapV() string { return x.IntfMapV }
+
+type testIntfMapT2 struct {
+	IntfMapV string
+}
+
+func (x testIntfMapT2) GetIntfMapV() string { return x.IntfMapV }
+
+// ----
+
 type testVerifyFlag uint8
 type testVerifyFlag uint8
 
 
 const (
 const (
@@ -2251,6 +2269,30 @@ func doTestScalars(t *testing.T, name string, h Handle) {
 	}
 	}
 }
 }
 
 
+func doTestIntfMapping(t *testing.T, name string, h Handle) {
+	testOnce.Do(testInitAll)
+	rti := reflect.TypeOf((*testIntfMapI)(nil)).Elem()
+	defer func() { h.getBasicHandle().Intf2Impl(rti, nil) }()
+
+	type T9 struct {
+		I testIntfMapI
+	}
+
+	for i, v := range []testIntfMapI{
+		&testIntfMapT1{"ABC"},
+		testIntfMapT2{"DEF"},
+	} {
+		if err := h.getBasicHandle().Intf2Impl(rti, reflect.TypeOf(v)); err != nil {
+			failT(t, "Error mapping %v to %T", rti, v)
+		}
+		var v1, v2 T9
+		v1 = T9{v}
+		b := testMarshalErr(v1, h, t, name+"-enc-"+strconv.Itoa(i))
+		testUnmarshalErr(&v2, b, h, t, name+"-dec-"+strconv.Itoa(i))
+		testDeepEqualErr(v1, v2, t, name+"-dec-eq-"+strconv.Itoa(i))
+	}
+}
+
 // -----------------
 // -----------------
 
 
 func TestJsonDecodeNonStringScalarInStringContext(t *testing.T) {
 func TestJsonDecodeNonStringScalarInStringContext(t *testing.T) {
@@ -2888,6 +2930,26 @@ func TestSimpleScalars(t *testing.T) {
 	doTestScalars(t, "simple", testSimpleH)
 	doTestScalars(t, "simple", testSimpleH)
 }
 }
 
 
+func TestJsonIntfMapping(t *testing.T) {
+	doTestIntfMapping(t, "json", testJsonH)
+}
+
+func TestCborIntfMapping(t *testing.T) {
+	doTestIntfMapping(t, "cbor", testCborH)
+}
+
+func TestMsgpackIntfMapping(t *testing.T) {
+	doTestIntfMapping(t, "msgpack", testMsgpackH)
+}
+
+func TestBincIntfMapping(t *testing.T) {
+	doTestIntfMapping(t, "binc", testBincH)
+}
+
+func TestSimpleIntfMapping(t *testing.T) {
+	doTestIntfMapping(t, "simple", testSimpleH)
+}
+
 // TODO:
 // TODO:
 //
 //
 // Add Tests for:
 // Add Tests for:

+ 15 - 15
codec/decode.go

@@ -952,7 +952,6 @@ func (d *Decoder) kInterfaceNaked(f *codecFnInfo) (rvn reflect.Value) {
 		return
 		return
 	}
 	}
 	// We cannot decode non-nil stream value into nil interface with methods (e.g. io.Reader).
 	// We cannot decode non-nil stream value into nil interface with methods (e.g. io.Reader).
-	// if num := f.ti.rt.NumMethod(); num > 0 {
 	if f.ti.numMeth > 0 {
 	if f.ti.numMeth > 0 {
 		d.errorf("cannot decode non-nil codec value into nil %v (%v methods)", f.ti.rt, f.ti.numMeth)
 		d.errorf("cannot decode non-nil codec value into nil %v (%v methods)", f.ti.rt, f.ti.numMeth)
 		return
 		return
@@ -1098,24 +1097,25 @@ func (d *Decoder) kInterface(f *codecFnInfo, rv reflect.Value) {
 
 
 	// every interface passed here MUST be settable.
 	// every interface passed here MUST be settable.
 	var rvn reflect.Value
 	var rvn reflect.Value
-	if rv.IsNil() {
-		if rvn = d.kInterfaceNaked(f); rvn.IsValid() {
-			rv.Set(rvn)
-		}
-		return
-	}
-	if d.h.InterfaceReset {
-		if rvn = d.kInterfaceNaked(f); rvn.IsValid() {
+	if rv.IsNil() || d.h.InterfaceReset {
+		// check if mapping to a type: if so, initialize it and move on
+		rvn = d.h.intf2impl(f.ti.rtid)
+		if rvn.IsValid() {
 			rv.Set(rvn)
 			rv.Set(rvn)
 		} else {
 		} else {
-			// reset to zero value based on current type in there.
-			rv.Set(reflect.Zero(rv.Elem().Type()))
+			rvn = d.kInterfaceNaked(f)
+			if rvn.IsValid() {
+				rv.Set(rvn)
+			} else if d.h.InterfaceReset {
+				// reset to zero value based on current type in there.
+				rv.Set(reflect.Zero(rv.Elem().Type()))
+			}
+			return
 		}
 		}
-		return
+	} else {
+		// now we have a non-nil interface value, meaning it contains a type
+		rvn = rv.Elem()
 	}
 	}
-
-	// now we have a non-nil interface value, meaning it contains a type
-	rvn = rv.Elem()
 	if d.d.TryDecodeAsNil() {
 	if d.d.TryDecodeAsNil() {
 		rv.Set(reflect.Zero(rvn.Type()))
 		rv.Set(reflect.Zero(rvn.Type()))
 		return
 		return

+ 6 - 10
codec/encode.go

@@ -162,10 +162,6 @@ type EncodeOptions struct {
 
 
 // ---------------------------------------------
 // ---------------------------------------------
 
 
-type simpleIoEncWriter struct {
-	io.Writer
-}
-
 // ioEncWriter implements encWriter and can write to an io.Writer implementation
 // ioEncWriter implements encWriter and can write to an io.Writer implementation
 type ioEncWriter struct {
 type ioEncWriter struct {
 	w  io.Writer
 	w  io.Writer
@@ -916,8 +912,9 @@ type Encoder struct {
 	// ---- writable fields during execution --- *try* to keep in sep cache line
 	// ---- writable fields during execution --- *try* to keep in sep cache line
 
 
 	// ---- cpu cache line boundary?
 	// ---- cpu cache line boundary?
-	b [scratchByteArrayLen]byte                 // used for encoding a chan or (non-addressable) array of bytes
-	_ [cacheLineSize - scratchByteArrayLen]byte // padding
+	// b [scratchByteArrayLen]byte
+	// _ [cacheLineSize - scratchByteArrayLen]byte // padding
+	b [cacheLineSize - 0]byte // used for encoding a chan or (non-addressable) array of bytes
 }
 }
 
 
 // NewEncoder returns an Encoder for encoding into an io.Writer.
 // NewEncoder returns an Encoder for encoding into an io.Writer.
@@ -1125,11 +1122,10 @@ func (e *Encoder) encode(iv interface{}) {
 		return
 		return
 	}
 	}
 
 
+	// a switch with only concrete types can be optimized.
+	// consequently, we deal with nil and interfaces outside.
+
 	switch v := iv.(type) {
 	switch v := iv.(type) {
-	// case nil:
-	// 	e.e.EncodeNil()
-	// case Selfer:
-	// 	v.CodecEncodeSelf(e)
 	case Raw:
 	case Raw:
 		e.rawBytes(v)
 		e.rawBytes(v)
 	case reflect.Value:
 	case reflect.Value:

+ 54 - 5
codec/helper.go

@@ -136,7 +136,9 @@ const (
 	cacheLineSize = 64
 	cacheLineSize = 64
 
 
 	wordSizeBits = strconv.IntSize
 	wordSizeBits = strconv.IntSize
-	wordSize     = strconv.IntSize / 8
+	wordSize     = wordSizeBits / 8
+
+	maxLevelsEmbedding = 15 // use this, so structFieldInfo fits into 8 bytes
 )
 )
 
 
 var (
 var (
@@ -420,6 +422,9 @@ type BasicHandle struct {
 	TypeInfos *TypeInfos
 	TypeInfos *TypeInfos
 
 
 	extHandle
 	extHandle
+
+	intf2impls
+
 	// xh *extHandle // consider making this a pointer, if needed for all handles to be comparable
 	// xh *extHandle // consider making this a pointer, if needed for all handles to be comparable
 	// xha [2]extTypeTagFn // init of xh. DONT DO THIS - makes this struct bigger by len*7 words
 	// xha [2]extTypeTagFn // init of xh. DONT DO THIS - makes this struct bigger by len*7 words
 
 
@@ -658,9 +663,9 @@ func (o *extHandle) SetExt(rt reflect.Type, tag uint64, ext Ext) (err error) {
 		// all natively supported type, so cannot have an extension
 		// all natively supported type, so cannot have an extension
 		return // TODO: should we silently ignore, or return an error???
 		return // TODO: should we silently ignore, or return an error???
 	}
 	}
-	if o == nil {
-		return errors.New("codec.Handle.SetExt: extHandle not initialized")
-	}
+	// if o == nil {
+	// 	return errors.New("codec.Handle.SetExt: extHandle not initialized")
+	// }
 	o2 := *o
 	o2 := *o
 	// if o2 == nil {
 	// if o2 == nil {
 	// 	return errors.New("codec.Handle.SetExt: extHandle not initialized")
 	// 	return errors.New("codec.Handle.SetExt: extHandle not initialized")
@@ -699,7 +704,51 @@ func (o extHandle) getExtForTag(tag uint64) *extTypeTagFn {
 	return nil
 	return nil
 }
 }
 
 
-const maxLevelsEmbedding = 15 // use this, so structFieldInfo fits into 8 bytes
+type intf2impl struct {
+	rtid uintptr // for intf
+	impl reflect.Type
+}
+
+type intf2impls []intf2impl
+
+// Intf2Impl maps an interface to an implementing type.
+// This allows us support infering the concrete type
+// and populating it when passed an interface.
+// e.g. var v io.Reader can be decoded as a bytes.Buffer, etc.
+//
+// Passing a nil impl will clear the mapping.
+func (o *intf2impls) Intf2Impl(intf, impl reflect.Type) (err error) {
+	if impl != nil && !impl.Implements(intf) {
+		return fmt.Errorf("Intf2Impl: %v does not implement %v", impl, intf)
+	}
+	rtid := rt2id(intf)
+	o2 := *o
+	for i := range o2 {
+		v := &o2[i]
+		if v.rtid == rtid {
+			v.impl = impl
+			return
+		}
+	}
+	*o = append(o2, intf2impl{rtid, impl})
+	return
+}
+
+func (o intf2impls) intf2impl(rtid uintptr) (rv reflect.Value) {
+	for i := range o {
+		v := &o[i]
+		if v.rtid == rtid {
+			if v.impl == nil {
+				return
+			}
+			if v.impl.Kind() == reflect.Ptr {
+				return reflect.New(v.impl.Elem())
+			}
+			return reflect.New(v.impl).Elem()
+		}
+	}
+	return
+}
 
 
 type structFieldInfo struct {
 type structFieldInfo struct {
 	encName   string // encode name
 	encName   string // encode name

+ 23 - 20
codec/helper_not_unsafe.go

@@ -35,29 +35,10 @@ func bytesView(v string) []byte {
 
 
 func definitelyNil(v interface{}) bool {
 func definitelyNil(v interface{}) bool {
 	// this is a best-effort option.
 	// this is a best-effort option.
-	// We just return false, so we don't unneessarily incur the cost of reflection this early.
+	// We just return false, so we don't unnecessarily incur the cost of reflection this early.
 	return false
 	return false
-	// rv := reflect.ValueOf(v)
-	// switch rv.Kind() {
-	// case reflect.Invalid:
-	// 	return true
-	// case reflect.Ptr, reflect.Interface, reflect.Chan, reflect.Slice, reflect.Map, reflect.Func:
-	// 	return rv.IsNil()
-	// default:
-	// 	return false
-	// }
 }
 }
 
 
-// // keepAlive4BytesView maintains a reference to the input parameter for bytesView.
-// //
-// // Usage: call this at point where done with the bytes view.
-// func keepAlive4BytesView(v string) {}
-
-// // keepAlive4BytesView maintains a reference to the input parameter for stringView.
-// //
-// // Usage: call this at point where done with the string view.
-// func keepAlive4StringView(v []byte) {}
-
 func rv2i(rv reflect.Value) interface{} {
 func rv2i(rv reflect.Value) interface{} {
 	return rv.Interface()
 	return rv.Interface()
 }
 }
@@ -237,3 +218,25 @@ func (e *Encoder) kUint64(f *codecFnInfo, rv reflect.Value) {
 func (e *Encoder) kUintptr(f *codecFnInfo, rv reflect.Value) {
 func (e *Encoder) kUintptr(f *codecFnInfo, rv reflect.Value) {
 	e.e.EncodeUint(rv.Uint())
 	e.e.EncodeUint(rv.Uint())
 }
 }
+
+// // keepAlive4BytesView maintains a reference to the input parameter for bytesView.
+// //
+// // Usage: call this at point where done with the bytes view.
+// func keepAlive4BytesView(v string) {}
+
+// // keepAlive4BytesView maintains a reference to the input parameter for stringView.
+// //
+// // Usage: call this at point where done with the string view.
+// func keepAlive4StringView(v []byte) {}
+
+// func definitelyNil(v interface{}) bool {
+// 	rv := reflect.ValueOf(v)
+// 	switch rv.Kind() {
+// 	case reflect.Invalid:
+// 		return true
+// 	case reflect.Ptr, reflect.Interface, reflect.Chan, reflect.Slice, reflect.Map, reflect.Func:
+// 		return rv.IsNil()
+// 	default:
+// 		return false
+// 	}
+// }

+ 58 - 41
codec/helper_unsafe.go

@@ -48,20 +48,16 @@ func stringView(v []byte) string {
 	if len(v) == 0 {
 	if len(v) == 0 {
 		return ""
 		return ""
 	}
 	}
-
 	bx := (*unsafeSlice)(unsafe.Pointer(&v))
 	bx := (*unsafeSlice)(unsafe.Pointer(&v))
-	sx := unsafeString{bx.Data, bx.Len}
-	return *(*string)(unsafe.Pointer(&sx))
+	return *(*string)(unsafe.Pointer(&unsafeString{bx.Data, bx.Len}))
 }
 }
 
 
 func bytesView(v string) []byte {
 func bytesView(v string) []byte {
 	if len(v) == 0 {
 	if len(v) == 0 {
 		return zeroByteSlice
 		return zeroByteSlice
 	}
 	}
-
 	sx := (*unsafeString)(unsafe.Pointer(&v))
 	sx := (*unsafeString)(unsafe.Pointer(&v))
-	bx := unsafeSlice{sx.Data, sx.Len, sx.Len}
-	return *(*[]byte)(unsafe.Pointer(&bx))
+	return *(*[]byte)(unsafe.Pointer(&unsafeSlice{sx.Data, sx.Len, sx.Len}))
 }
 }
 
 
 func definitelyNil(v interface{}) bool {
 func definitelyNil(v interface{}) bool {
@@ -78,43 +74,23 @@ func definitelyNil(v interface{}) bool {
 	//                 except it's a method value (e.g. r.Read, which implies that it is a Func)
 	//                 except it's a method value (e.g. r.Read, which implies that it is a Func)
 
 
 	return ((*unsafeIntf)(unsafe.Pointer(&v))).word == nil
 	return ((*unsafeIntf)(unsafe.Pointer(&v))).word == nil
-
-	// var ui *unsafeIntf = (*unsafeIntf)(unsafe.Pointer(&v))
-	// if ui.word == nil {
-	// 	return true
-	// }
-	// var tk = reflect.TypeOf(v).Kind()
-	// return (tk == reflect.Interface || tk == reflect.Slice) && *(*unsafe.Pointer)(ui.word) == nil
-	// fmt.Printf(">>>> definitely nil: isnil: %v, TYPE: \t%T, word: %v, *word: %v, type: %v, nil: %v\n",
-	// v == nil, v, word, *((*unsafe.Pointer)(word)), ui.typ, nil)
 }
 }
 
 
-// func keepAlive4BytesView(v string) {
-// 	runtime.KeepAlive(v)
-// }
-
-// func keepAlive4StringView(v []byte) {
-// 	runtime.KeepAlive(v)
-// }
-
-// TODO: consider a more generally-known optimization for reflect.Value ==> Interface
-//
-// Currently, we use this fragile method that taps into implememtation details from
-// the source go stdlib reflect/value.go, and trims the implementation.
 func rv2i(rv reflect.Value) interface{} {
 func rv2i(rv reflect.Value) interface{} {
+	// TODO: consider a more generally-known optimization for reflect.Value ==> Interface
+	//
+	// Currently, we use this fragile method that taps into implememtation details from
+	// the source go stdlib reflect/value.go, and trims the implementation.
+
 	urv := (*unsafeReflectValue)(unsafe.Pointer(&rv))
 	urv := (*unsafeReflectValue)(unsafe.Pointer(&rv))
 	// true references (map, func, chan, ptr - NOT slice) may be double-referenced as flagIndir
 	// true references (map, func, chan, ptr - NOT slice) may be double-referenced as flagIndir
 	var ptr unsafe.Pointer
 	var ptr unsafe.Pointer
-	// kk := reflect.Kind(urv.flag & (1<<5 - 1))
-	// if (kk == reflect.Map || kk == reflect.Ptr || kk == reflect.Chan || kk == reflect.Func) && urv.flag&unsafeFlagIndir != 0 {
 	if refBitset.isset(byte(urv.flag&(1<<5-1))) && urv.flag&unsafeFlagIndir != 0 {
 	if refBitset.isset(byte(urv.flag&(1<<5-1))) && urv.flag&unsafeFlagIndir != 0 {
 		ptr = *(*unsafe.Pointer)(urv.ptr)
 		ptr = *(*unsafe.Pointer)(urv.ptr)
 	} else {
 	} else {
 		ptr = urv.ptr
 		ptr = urv.ptr
 	}
 	}
 	return *(*interface{})(unsafe.Pointer(&unsafeIntf{typ: urv.typ, word: ptr}))
 	return *(*interface{})(unsafe.Pointer(&unsafeIntf{typ: urv.typ, word: ptr}))
-	// return *(*interface{})(unsafe.Pointer(&unsafeIntf{word: *(*unsafe.Pointer)(urv.ptr), typ: urv.typ}))
-	// return *(*interface{})(unsafe.Pointer(&unsafeIntf{word: urv.ptr, typ: urv.typ}))
 }
 }
 
 
 func rt2id(rt reflect.Type) uintptr {
 func rt2id(rt reflect.Type) uintptr {
@@ -129,14 +105,8 @@ func i2rtid(i interface{}) uintptr {
 	return uintptr(((*unsafeIntf)(unsafe.Pointer(&i))).typ)
 	return uintptr(((*unsafeIntf)(unsafe.Pointer(&i))).typ)
 }
 }
 
 
-// func rv0t(rt reflect.Type) reflect.Value {
-// 	ut := (*unsafeIntf)(unsafe.Pointer(&rt))
-// 	// we need to determine whether ifaceIndir, and then whether to just pass 0 as the ptr
-// 	uv := unsafeReflectValue{ut.word, &zeroRTv, flag(rt.Kind())}
-// 	return *(*reflect.Value)(unsafe.Pointer(&uv})
-// }
-
 // --------------------------
 // --------------------------
+
 type atomicTypeInfoSlice struct { // expected to be 2 words
 type atomicTypeInfoSlice struct { // expected to be 2 words
 	v unsafe.Pointer
 	v unsafe.Pointer
 	_ [8]byte // padding
 	_ [8]byte // padding
@@ -153,9 +123,6 @@ func (x *atomicTypeInfoSlice) store(p *[]rtid2ti) {
 // --------------------------
 // --------------------------
 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))
-	// if urv.flag&unsafeFlagIndir != 0 {
-	// 	urv.ptr = *(*unsafe.Pointer)(urv.ptr)
-	// }
 	*(*[]byte)(urv.ptr) = d.rawBytes()
 	*(*[]byte)(urv.ptr) = d.rawBytes()
 }
 }
 
 
@@ -327,6 +294,56 @@ func (e *Encoder) kUintptr(f *codecFnInfo, rv reflect.Value) {
 
 
 // ------------
 // ------------
 
 
+// func (d *Decoder) raw(f *codecFnInfo, rv reflect.Value) {
+// 	urv := (*unsafeReflectValue)(unsafe.Pointer(&rv))
+// 	// if urv.flag&unsafeFlagIndir != 0 {
+// 	// 	urv.ptr = *(*unsafe.Pointer)(urv.ptr)
+// 	// }
+// 	*(*[]byte)(urv.ptr) = d.rawBytes()
+// }
+
+// func rv0t(rt reflect.Type) reflect.Value {
+// 	ut := (*unsafeIntf)(unsafe.Pointer(&rt))
+// 	// we need to determine whether ifaceIndir, and then whether to just pass 0 as the ptr
+// 	uv := unsafeReflectValue{ut.word, &zeroRTv, flag(rt.Kind())}
+// 	return *(*reflect.Value)(unsafe.Pointer(&uv})
+// }
+
+// func rv2i(rv reflect.Value) interface{} {
+// 	urv := (*unsafeReflectValue)(unsafe.Pointer(&rv))
+// 	// true references (map, func, chan, ptr - NOT slice) may be double-referenced as flagIndir
+// 	var ptr unsafe.Pointer
+// 	// kk := reflect.Kind(urv.flag & (1<<5 - 1))
+// 	// if (kk == reflect.Map || kk == reflect.Ptr || kk == reflect.Chan || kk == reflect.Func) && urv.flag&unsafeFlagIndir != 0 {
+// 	if refBitset.isset(byte(urv.flag&(1<<5-1))) && urv.flag&unsafeFlagIndir != 0 {
+// 		ptr = *(*unsafe.Pointer)(urv.ptr)
+// 	} else {
+// 		ptr = urv.ptr
+// 	}
+// 	return *(*interface{})(unsafe.Pointer(&unsafeIntf{typ: urv.typ, word: ptr}))
+// 	// return *(*interface{})(unsafe.Pointer(&unsafeIntf{word: *(*unsafe.Pointer)(urv.ptr), typ: urv.typ}))
+// 	// return *(*interface{})(unsafe.Pointer(&unsafeIntf{word: urv.ptr, typ: urv.typ}))
+// }
+
+// func definitelyNil(v interface{}) bool {
+// 	var ui *unsafeIntf = (*unsafeIntf)(unsafe.Pointer(&v))
+// 	if ui.word == nil {
+// 		return true
+// 	}
+// 	var tk = reflect.TypeOf(v).Kind()
+// 	return (tk == reflect.Interface || tk == reflect.Slice) && *(*unsafe.Pointer)(ui.word) == nil
+// 	fmt.Printf(">>>> definitely nil: isnil: %v, TYPE: \t%T, word: %v, *word: %v, type: %v, nil: %v\n",
+// 	v == nil, v, word, *((*unsafe.Pointer)(word)), ui.typ, nil)
+// }
+
+// func keepAlive4BytesView(v string) {
+// 	runtime.KeepAlive(v)
+// }
+
+// func keepAlive4StringView(v []byte) {
+// 	runtime.KeepAlive(v)
+// }
+
 // func rt2id(rt reflect.Type) uintptr {
 // func rt2id(rt reflect.Type) uintptr {
 // 	return uintptr(((*unsafeIntf)(unsafe.Pointer(&rt))).word)
 // 	return uintptr(((*unsafeIntf)(unsafe.Pointer(&rt))).word)
 // 	// var i interface{} = rt
 // 	// var i interface{} = rt

+ 1 - 0
codec/rpc.go

@@ -17,6 +17,7 @@ type Rpc interface {
 	ClientCodec(conn io.ReadWriteCloser, h Handle) rpc.ClientCodec
 	ClientCodec(conn io.ReadWriteCloser, h Handle) rpc.ClientCodec
 }
 }
 
 
+// RPCOptions holds options specific to rpc functionality
 type RPCOptions struct {
 type RPCOptions struct {
 	// RPCNoBuffer configures whether we attempt to buffer reads and writes during RPC calls.
 	// RPCNoBuffer configures whether we attempt to buffer reads and writes during RPC calls.
 	//
 	//

+ 6 - 0
codec/z_all_test.go

@@ -229,6 +229,11 @@ func testCodecGroup(t *testing.T) {
 	t.Run("TestMsgpackScalars", TestMsgpackScalars)
 	t.Run("TestMsgpackScalars", TestMsgpackScalars)
 	t.Run("TestBincScalars", TestBincScalars)
 	t.Run("TestBincScalars", TestBincScalars)
 	t.Run("TestSimpleScalars", TestSimpleScalars)
 	t.Run("TestSimpleScalars", TestSimpleScalars)
+	t.Run("TestJsonIntfMapping", TestJsonIntfMapping)
+	t.Run("TestCborIntfMapping", TestCborIntfMapping)
+	t.Run("TestMsgpackIntfMapping", TestMsgpackIntfMapping)
+	t.Run("TestBincIntfMapping", TestBincIntfMapping)
+	t.Run("TestSimpleIntfMapping", TestSimpleIntfMapping)
 
 
 	t.Run("TestJsonInvalidUnicode", TestJsonInvalidUnicode)
 	t.Run("TestJsonInvalidUnicode", TestJsonInvalidUnicode)
 	t.Run("TestCborHalfFloat", TestCborHalfFloat)
 	t.Run("TestCborHalfFloat", TestCborHalfFloat)
@@ -260,6 +265,7 @@ func testJsonGroup(t *testing.T) {
 	t.Run("TestJsonUintToInt", TestJsonUintToInt)
 	t.Run("TestJsonUintToInt", TestJsonUintToInt)
 	t.Run("TestJsonDifferentMapOrSliceType", TestJsonDifferentMapOrSliceType)
 	t.Run("TestJsonDifferentMapOrSliceType", TestJsonDifferentMapOrSliceType)
 	t.Run("TestJsonScalars", TestJsonScalars)
 	t.Run("TestJsonScalars", TestJsonScalars)
+	t.Run("TestJsonIntfMapping", TestJsonIntfMapping)
 }
 }
 
 
 func testBincGroup(t *testing.T) {
 func testBincGroup(t *testing.T) {