Browse Source

codec: refactor codecgen, optimize isEmptyValue and cache more static type info

codecgen
- better buffering and heuristics to infer initial buffer size using num fields
- reduce allocation where un-necessary during decode
  previously, we would initialize embedded pointers, and then
  check if decodeAsNil, and if so, set it back to nil.
  Now, we only initialize if decodeasnil=false.
- reduce allocation by not creating unnecessary temporary variables
  to track the address of a struct field or slice/map index operation.
- let (en|de)c[Var] methods take an isptr parameter,
  and use it to determine how to evaluate things
- use the struct variable directly if possible
  previously, we would always create a temp variable for the address,
  and then pass it on. Now, we just use the variable dotted name directly.
- pass time.Time as a value, so we don't have to put it on the heap unnecessarily.
  previously, we passed all structs as pointers to encode and decode, to reduce
  memory allocations, etc.
- leverage typeInfo, so no need to make expensive reflect.Implements calls
- other misc performance improvements

cache more static type info
- reflect.Type Elem, Key, ChanDir, Kind, PkgPath
- whether a type implements IsZero() bool

optimize isEmptyValue
- create 2 diff implementations of isEmpty for safe vs unsafe mode
- optimize isEmptyValue leveraging typeInfo
Ugorji Nwoke 8 years ago
parent
commit
d41456c8f4

+ 25 - 4
codec/codecgen/gen.go

@@ -55,20 +55,33 @@ import (
 )
 
 func CodecGenTempWrite{{ .RandString }}() {
+	os.Remove("{{ .OutFile }}")
 	fout, err := os.Create("{{ .OutFile }}")
 	if err != nil {
 		panic(err)
 	}
 	defer fout.Close()
-	var out bytes.Buffer
 	
-	var typs []reflect.Type 
+	var typs []reflect.Type
+	var typ reflect.Type
+	var numfields int
 {{ range $index, $element := .Types }}
 	var t{{ $index }} {{ . }}
-	typs = append(typs, reflect.TypeOf(t{{ $index }}))
+typ = reflect.TypeOf(t{{ $index }})
+	typs = append(typs, typ)
+	if typ.Kind() == reflect.Struct { numfields += typ.NumField() } else { numfields += 1 }
 {{ end }}
-	{{ if not .CodecPkgFiles }}{{ .CodecPkgName }}.{{ end }}Gen(&out, "{{ .BuildTag }}", "{{ .PackageName }}", "{{ .RandString }}", {{ .NoExtensions }}, {{ if not .CodecPkgFiles }}{{ .CodecPkgName }}.{{ end }}NewTypeInfos(strings.Split("{{ .StructTags }}", ",")), typs...)
+
+	// println("initializing {{ .OutFile }}, buf size: {{ .AllFilesSize }}*16",
+	// 	{{ .AllFilesSize }}*16, "num fields: ", numfields)
+	var out = bytes.NewBuffer(make([]byte, 0, numfields*1024)) // {{ .AllFilesSize }}*16
+	{{ if not .CodecPkgFiles }}{{ .CodecPkgName }}.{{ end }}Gen(out,
+		"{{ .BuildTag }}", "{{ .PackageName }}", "{{ .RandString }}", {{ .NoExtensions }},
+		{{ if not .CodecPkgFiles }}{{ .CodecPkgName }}.{{ end }}NewTypeInfos(strings.Split("{{ .StructTags }}", ",")),
+		 typs...)
+
 	bout, err := format.Source(out.Bytes())
+	// println("... lengths: before formatting: ", len(out.Bytes()), ", after formatting", len(bout))
 	if err != nil {
 		fout.Write(out.Bytes())
 		panic(err)
@@ -129,6 +142,7 @@ func Generate(outfile, buildTag, codecPkgPath string,
 		BuildTag        string
 		StructTags      string
 		Types           []string
+		AllFilesSize    int64
 		CodecPkgFiles   bool
 		NoExtensions    bool
 	}
@@ -150,11 +164,17 @@ func Generate(outfile, buildTag, codecPkgPath string,
 		tv.ImportPath = stripVendor(tv.ImportPath)
 	}
 	astfiles := make([]*ast.File, len(infiles))
+	var fi os.FileInfo
 	for i, infile := range infiles {
 		if filepath.Dir(infile) != lastdir {
 			err = errors.New("in files must all be in same directory as outfile")
 			return
 		}
+		if fi, err = os.Stat(infile); err != nil {
+			return
+		}
+		tv.AllFilesSize += fi.Size()
+
 		fset := token.NewFileSet()
 		astfiles[i], err = parser.ParseFile(fset, infile, nil, 0)
 		if err != nil {
@@ -295,6 +315,7 @@ func gen1(frunName, tmplStr string, tv interface{}) (frun *os.File, err error) {
 	}
 	bw := bufio.NewWriter(frun)
 	if err = t.Execute(bw, tv); err != nil {
+		bw.Flush()
 		return
 	}
 	if err = bw.Flush(); err != nil {

+ 4 - 3
codec/decode.go

@@ -1236,11 +1236,11 @@ func (d *Decoder) kSlice(f *codecFnInfo, rv reflect.Value) {
 	// A slice can be set from a map or array in stream.
 	// This way, the order can be kept (as order is lost with map).
 	ti := f.ti
-	if f.seq == seqTypeChan && ti.rt.ChanDir()&reflect.SendDir == 0 {
+	if f.seq == seqTypeChan && ti.chandir&uint8(reflect.SendDir) == 0 {
 		d.errorf("receive-only channel cannot be used for sending byte(s)")
 	}
 	dd := d.d
-	rtelem0 := ti.rt.Elem()
+	rtelem0 := ti.elem
 	ctyp := dd.ContainerType()
 	if ctyp == valueTypeBytes || ctyp == valueTypeString {
 		// you can only decode bytes or string in the stream into a slice or array of bytes
@@ -1475,12 +1475,13 @@ func (d *Decoder) kMap(f *codecFnInfo, rv reflect.Value) {
 		return
 	}
 
-	ktype, vtype := ti.rt.Key(), ti.rt.Elem()
+	ktype, vtype := ti.key, ti.elem
 	ktypeId := rt2id(ktype)
 	vtypeKind := vtype.Kind()
 
 	var keyFn, valFn *codecFn
 	var ktypeLo, vtypeLo reflect.Type
+
 	for ktypeLo = ktype; ktypeLo.Kind() == reflect.Ptr; ktypeLo = ktypeLo.Elem() {
 	}
 

+ 6 - 6
codec/encode.go

@@ -311,12 +311,12 @@ func (e *Encoder) kSlice(f *codecFnInfo, rv reflect.Value) {
 			return
 		}
 	}
-	if f.seq == seqTypeChan && ti.rt.ChanDir()&reflect.RecvDir == 0 {
+	if f.seq == seqTypeChan && ti.chandir&uint8(reflect.RecvDir) == 0 {
 		e.errorf("send-only channel cannot be used for receiving byte(s)")
 	}
 	elemsep := e.esep
 	l := rv.Len()
-	rtelem := ti.rt.Elem()
+	rtelem := ti.elem
 	rtelemIsByte := uint8TypId == rt2id(rtelem) // NOT rtelem.Kind() == reflect.Uint8
 	// if a slice, array or chan of bytes, treat specially
 	if rtelemIsByte {
@@ -526,14 +526,14 @@ func (e *Encoder) kStruct(f *codecFnInfo, rv reflect.Value) {
 		// kv.r = si.field(rv, false)
 		kv.r = sfn.field(si)
 		if toMap {
-			if si.omitEmpty() && isEmptyValue(kv.r, recur, recur) {
+			if si.omitEmpty() && isEmptyValue(kv.r, e.h.TypeInfos, recur, recur) {
 				continue
 			}
 			kv.v = si.encName
 		} else {
 			// use the zero value.
 			// if a reference or struct, set to nil (so you do not output too much)
-			if si.omitEmpty() && isEmptyValue(kv.r, recur, recur) {
+			if si.omitEmpty() && isEmptyValue(kv.r, e.h.TypeInfos, recur, recur) {
 				switch kv.r.Kind() {
 				case reflect.Struct, reflect.Interface, reflect.Ptr, reflect.Array, reflect.Map, reflect.Slice:
 					kv.r = reflect.Value{} //encode as nil
@@ -611,9 +611,9 @@ func (e *Encoder) kMap(f *codecFnInfo, rv reflect.Value) {
 	// a concrete type and kInterface will bomb.
 	var keyFn, valFn *codecFn
 	ti := f.ti
-	rtkey0 := ti.rt.Key()
+	rtkey0 := ti.key
 	rtkey := rtkey0
-	rtval0 := ti.rt.Elem()
+	rtval0 := ti.elem
 	rtval := rtval0
 	// rtkeyid := rt2id(rtkey0)
 	for rtval.Kind() == reflect.Ptr {

+ 1 - 1
codec/gen-dec-array.go.tmpl

@@ -43,7 +43,7 @@ if {{var "l"}} == 0 {
 			{{var "c"}} = true 
 		}{{end}}
 		{{var "h"}}.ElemContainerState({{var "j"}})
-        // {{var "dn"}} = r.TryDecodeAsNil()
+        {{/* {{var "dn"}} = r.TryDecodeAsNil() */}}
         {{if isChan}}{{ $x := printf "%[1]vv%[2]v" .TempVar .Rand }}var {{var $x}} {{ .Typ }}
 		{{ decLineVar $x }}
 		{{var "v"}} <- {{ $x }}

+ 1 - 1
codec/gen.generated.go

@@ -98,7 +98,7 @@ if {{var "l"}} == 0 {
 			{{var "c"}} = true 
 		}{{end}}
 		{{var "h"}}.ElemContainerState({{var "j"}})
-        // {{var "dn"}} = r.TryDecodeAsNil()
+        {{/* {{var "dn"}} = r.TryDecodeAsNil() */}}
         {{if isChan}}{{ $x := printf "%[1]vv%[2]v" .TempVar .Rand }}var {{var $x}} {{ .Typ }}
 		{{ decLineVar $x }}
 		{{var "v"}} <- {{ $x }}

+ 290 - 225
codec/gen.go

@@ -139,9 +139,15 @@ type genBuf struct {
 	buf []byte
 }
 
-func (x *genBuf) s(s string) *genBuf { x.buf = append(x.buf, s...); return x }
-func (x *genBuf) b(s []byte) *genBuf { x.buf = append(x.buf, s...); return x }
-func (x *genBuf) v() string          { return string(x.buf) }
+func (x *genBuf) s(s string) *genBuf              { x.buf = append(x.buf, s...); return x }
+func (x *genBuf) b(s []byte) *genBuf              { x.buf = append(x.buf, s...); return x }
+func (x *genBuf) v() string                       { return string(x.buf) }
+func (x *genBuf) f(s string, args ...interface{}) { x.s(fmt.Sprintf(s, args...)) }
+func (x *genBuf) reset() {
+	if x.buf != nil {
+		x.buf = x.buf[:0]
+	}
+}
 
 // genRunner holds some state used during a Gen run.
 type genRunner struct {
@@ -410,13 +416,6 @@ func (x *genRunner) genRefPkgs(t reflect.Type) {
 	}
 }
 
-func (x *genRunner) line(s string) {
-	x.out(s)
-	if len(s) == 0 || s[len(s)-1] != '\n' {
-		x.out("\n")
-	}
-}
-
 func (x *genRunner) varsfx() string {
 	x.c++
 	return strconv.FormatUint(x.c, 10)
@@ -427,17 +426,31 @@ func (x *genRunner) varsfxreset() {
 }
 
 func (x *genRunner) out(s string) {
-	if _, err := io.WriteString(x.w, s); err != nil {
+	_, err := io.WriteString(x.w, s)
+	if err != nil {
 		panic(err)
 	}
 }
 
-func (x *genRunner) linef(s string, params ...interface{}) {
-	x.line(fmt.Sprintf(s, params...))
+func (x *genRunner) outf(s string, params ...interface{}) {
+	_, err := fmt.Fprintf(x.w, s, params...)
+	if err != nil {
+		panic(err)
+	}
 }
 
-func (x *genRunner) outf(s string, params ...interface{}) {
-	x.out(fmt.Sprintf(s, params...))
+func (x *genRunner) line(s string) {
+	x.out(s)
+	if len(s) == 0 || s[len(s)-1] != '\n' {
+		x.out("\n")
+	}
+}
+
+func (x *genRunner) linef(s string, params ...interface{}) {
+	x.outf(s, params...)
+	if len(s) == 0 || s[len(s)-1] != '\n' {
+		x.out("\n")
+	}
 }
 
 func (x *genRunner) genTypeName(t reflect.Type) (n string) {
@@ -510,16 +523,17 @@ func (x *genRunner) selfer(encode bool) {
 	t := x.tc
 	t0 := t
 	// always make decode use a pointer receiver,
-	// and structs always use a ptr receiver (encode|decode)
-	isptr := !encode || (t.Kind() == reflect.Struct || t.Kind() == reflect.Array)
+	// and structs/arrays always use a ptr receiver (encode|decode)
+	isptr := !encode || t.Kind() == reflect.Array || (t.Kind() == reflect.Struct && t != timeTyp)
 	x.varsfxreset()
-	fnSigPfx := "func (x "
+
+	fnSigPfx := "func (" + genTopLevelVarName + " "
 	if isptr {
 		fnSigPfx += "*"
 	}
 	fnSigPfx += x.genTypeName(t)
-
 	x.out(fnSigPfx)
+
 	if isptr {
 		t = reflect.PtrTo(t)
 	}
@@ -535,7 +549,7 @@ func (x *genRunner) selfer(encode bool) {
 		// or way to elegantly handle that, and also setting it to a
 		// non-nil value doesn't affect the pointer passed.
 		// x.decVar(genTopLevelVarName, t, false)
-		x.dec(genTopLevelVarName, t0)
+		x.dec(genTopLevelVarName, t0, true)
 	}
 	x.line("}")
 	x.line("")
@@ -579,11 +593,17 @@ func (x *genRunner) selfer(encode bool) {
 }
 
 // used for chan, array, slice, map
-func (x *genRunner) xtraSM(varname string, encode bool, t reflect.Type) {
+func (x *genRunner) xtraSM(varname string, t reflect.Type, encode, isptr bool) {
+	var ptrPfx, addrPfx string
+	if isptr {
+		ptrPfx = "*"
+	} else {
+		addrPfx = "&"
+	}
 	if encode {
-		x.linef("h.enc%s((%s%s)(%s), e)", x.genMethodNameT(t), x.arr2str(t, "*"), x.genTypeName(t), varname)
+		x.linef("h.enc%s((%s%s)(%s), e)", x.genMethodNameT(t), ptrPfx, x.genTypeName(t), varname)
 	} else {
-		x.linef("h.dec%s((*%s)(%s), d)", x.genMethodNameT(t), x.genTypeName(t), varname)
+		x.linef("h.dec%s((*%s)(%s%s), d)", x.genMethodNameT(t), x.genTypeName(t), addrPfx, varname)
 	}
 	x.registerXtraT(t)
 }
@@ -622,17 +642,23 @@ func (x *genRunner) encVar(varname string, t reflect.Type) {
 	if checkNil {
 		x.linef("if %s == nil { r.EncodeNil() } else { ", varname)
 	}
+
 	switch t.Kind() {
 	case reflect.Ptr:
-		switch t.Elem().Kind() {
-		case reflect.Struct, reflect.Array:
+		telem := t.Elem()
+		tek := telem.Kind()
+		if tek == reflect.Array || (tek == reflect.Struct && t != timeTyp) {
 			x.enc(varname, genNonPtr(t))
-		default:
-			i := x.varsfx()
-			x.line(genTempVarPfx + i + " := *" + varname)
-			x.enc(genTempVarPfx+i, genNonPtr(t))
+			break
 		}
+		i := x.varsfx()
+		x.line(genTempVarPfx + i + " := *" + varname)
+		x.enc(genTempVarPfx+i, genNonPtr(t))
 	case reflect.Struct, reflect.Array:
+		if t == timeTyp {
+			x.enc(varname, t)
+			break
+		}
 		i := x.varsfx()
 		x.line(genTempVarPfx + i + " := &" + varname)
 		x.enc(genTempVarPfx+i, t)
@@ -646,29 +672,33 @@ func (x *genRunner) encVar(varname string, t reflect.Type) {
 
 }
 
-// enc will encode a variable (varname) of type t,
-// except t is of kind reflect.Struct or reflect.Array, wherein varname is of type ptrTo(T) (to prevent copying)
+// enc will encode a variable (varname) of type t, where t represents T.
+// if t is !time.Time and t is of kind reflect.Struct or reflect.Array, varname is of type *T
+// (to prevent copying),
+// else t is of type T
 func (x *genRunner) enc(varname string, t reflect.Type) {
 	rtid := rt2id(t)
+	ti2 := x.ti.get(rtid, t)
 	// We call CodecEncodeSelf if one of the following are honored:
 	//   - the type already implements Selfer, call that
 	//   - the type has a Selfer implementation just created, use that
 	//   - the type is in the list of the ones we will generate for, but it is not currently being generated
 
 	mi := x.varsfx()
-	tptr := reflect.PtrTo(t)
+	// tptr := reflect.PtrTo(t)
 	tk := t.Kind()
 	if x.checkForSelfer(t, varname) {
-		if tk == reflect.Array || tk == reflect.Struct { // varname is of type *T
-			if tptr.Implements(selferTyp) || t.Implements(selferTyp) {
+		if tk == reflect.Array || (tk == reflect.Struct && rtid != timeTypId) { // varname is of type *T
+			// if tptr.Implements(selferTyp) || t.Implements(selferTyp) {
+			if ti2.isFlag(typeInfoFlagIsZeroerPtr) || ti2.isFlag(typeInfoFlagIsZeroer) {
 				x.line(varname + ".CodecEncodeSelf(e)")
 				return
 			}
 		} else { // varname is of type T
-			if t.Implements(selferTyp) {
+			if ti2.cs { // t.Implements(selferTyp) {
 				x.line(varname + ".CodecEncodeSelf(e)")
 				return
-			} else if tptr.Implements(selferTyp) {
+			} else if ti2.csp { // tptr.Implements(selferTyp) {
 				x.linef("%ssf%s := &%s", genTempVarPfx, mi, varname)
 				x.linef("%ssf%s.CodecEncodeSelf(e)", genTempVarPfx, mi)
 				return
@@ -707,7 +737,7 @@ func (x *genRunner) enc(varname string, t reflect.Type) {
 	defer func() { x.line("}") }() //end if block
 
 	if t == timeTyp {
-		x.linef("} else { r.EncodeTime(*%s)", varname)
+		x.linef("} else { r.EncodeTime(%s)", varname)
 		return
 	}
 	if t == rawTyp {
@@ -725,27 +755,27 @@ func (x *genRunner) enc(varname string, t reflect.Type) {
 		x.linef("} else if %s := z.Extension(z.I2Rtid(%s)); %s != nil { z.EncExtension(%s, %s) ", yy, varname, yy, varname, yy)
 	}
 	if arrayOrStruct { // varname is of type *T
-		if t.Implements(binaryMarshalerTyp) || tptr.Implements(binaryMarshalerTyp) {
+		if ti2.bm || ti2.bmp { // t.Implements(binaryMarshalerTyp) || tptr.Implements(binaryMarshalerTyp) {
 			x.linef("} else if z.EncBinary() { z.EncBinaryMarshal(%v) ", varname)
 		}
-		if t.Implements(jsonMarshalerTyp) || tptr.Implements(jsonMarshalerTyp) {
+		if ti2.jm || ti2.jmp { // t.Implements(jsonMarshalerTyp) || tptr.Implements(jsonMarshalerTyp) {
 			x.linef("} else if !z.EncBinary() && z.IsJSONHandle() { z.EncJSONMarshal(%v) ", varname)
-		} else if t.Implements(textMarshalerTyp) || tptr.Implements(textMarshalerTyp) {
+		} else if ti2.tm || ti2.tmp { // t.Implements(textMarshalerTyp) || tptr.Implements(textMarshalerTyp) {
 			x.linef("} else if !z.EncBinary() { z.EncTextMarshal(%v) ", varname)
 		}
 	} else { // varname is of type T
-		if t.Implements(binaryMarshalerTyp) {
+		if ti2.bm { // t.Implements(binaryMarshalerTyp) {
 			x.linef("} else if z.EncBinary() { z.EncBinaryMarshal(%v) ", varname)
-		} else if tptr.Implements(binaryMarshalerTyp) {
+		} else if ti2.bmp { // tptr.Implements(binaryMarshalerTyp) {
 			x.linef("} else if z.EncBinary() { z.EncBinaryMarshal(&%v) ", varname)
 		}
-		if t.Implements(jsonMarshalerTyp) {
+		if ti2.jm { // t.Implements(jsonMarshalerTyp) {
 			x.linef("} else if !z.EncBinary() && z.IsJSONHandle() { z.EncJSONMarshal(%v) ", varname)
-		} else if tptr.Implements(jsonMarshalerTyp) {
+		} else if ti2.jmp { // tptr.Implements(jsonMarshalerTyp) {
 			x.linef("} else if !z.EncBinary() && z.IsJSONHandle() { z.EncJSONMarshal(&%v) ", varname)
-		} else if t.Implements(textMarshalerTyp) {
+		} else if ti2.tm { // t.Implements(textMarshalerTyp) {
 			x.linef("} else if !z.EncBinary() { z.EncTextMarshal(%v) ", varname)
-		} else if tptr.Implements(textMarshalerTyp) {
+		} else if ti2.tmp { // tptr.Implements(textMarshalerTyp) {
 			x.linef("} else if !z.EncBinary() { z.EncTextMarshal(&%v) ", varname)
 		}
 	}
@@ -765,10 +795,10 @@ func (x *genRunner) enc(varname string, t reflect.Type) {
 	case reflect.String:
 		x.line("r.EncodeString(codecSelferCcUTF8" + x.xs + ", string(" + varname + "))")
 	case reflect.Chan:
-		x.xtraSM(varname, true, t)
+		x.xtraSM(varname, t, true, false)
 		// x.encListFallback(varname, rtid, t)
 	case reflect.Array:
-		x.xtraSM(varname, true, t)
+		x.xtraSM(varname, t, true, true)
 	case reflect.Slice:
 		// if nil, call dedicated function
 		// if a []uint8, call dedicated function
@@ -782,7 +812,7 @@ func (x *genRunner) enc(varname string, t reflect.Type) {
 			g := x.newGenV(t)
 			x.line("z.F." + g.MethodNamePfx("Enc", false) + "V(" + varname + ", e)")
 		} else {
-			x.xtraSM(varname, true, t)
+			x.xtraSM(varname, t, true, false)
 			// x.encListFallback(varname, rtid, t)
 		}
 	case reflect.Map:
@@ -796,7 +826,7 @@ func (x *genRunner) enc(varname string, t reflect.Type) {
 			g := x.newGenV(t)
 			x.line("z.F." + g.MethodNamePfx("Enc", false) + "V(" + varname + ", e)")
 		} else {
-			x.xtraSM(varname, true, t)
+			x.xtraSM(varname, t, true, false)
 			// x.encMapFallback(varname, rtid, t)
 		}
 	case reflect.Struct:
@@ -839,15 +869,21 @@ func (x *genRunner) encOmitEmptyLine(t2 reflect.StructField, varname string, buf
 	varname2 := varname + "." + t2.Name
 	switch t2.Type.Kind() {
 	case reflect.Struct:
+		rtid2 := rt2id(t2.Type)
+		ti2 := x.ti.get(rtid2, t2.Type)
 		// fmt.Printf(">>>> structfield: omitempty: type: %s, field: %s\n", t2.Type.Name(), t2.Name)
-		if t2.Type.Comparable() {
-			buf.s(varname2).s(" != ").s(x.genZeroValueR(t2.Type))
+		if ti2.rtid == timeTypId {
+			buf.s("!(").s(varname2).s(".IsZero())")
 			break
 		}
-		if t2.Type.Implements(iszeroTyp) || reflect.PtrTo(t2.Type).Implements(iszeroTyp) {
+		if ti2.isFlag(typeInfoFlagIsZeroerPtr) || ti2.isFlag(typeInfoFlagIsZeroer) {
 			buf.s("!(").s(varname2).s(".IsZero())")
 			break
 		}
+		if ti2.isFlag(typeInfoFlagComparable) {
+			buf.s(varname2).s(" != ").s(x.genZeroValueR(t2.Type))
+			break
+		}
 		// buf.s("(")
 		buf.s("false")
 		for i, n := 0, t2.Type.NumField(); i < n; i++ {
@@ -1082,85 +1118,160 @@ func (x *genRunner) encMapFallback(varname string, t reflect.Type) {
 	x.line("r.WriteMapEnd()")
 }
 
-func (x *genRunner) decVar(varname, decodedNilVarname string, t reflect.Type, canBeNil bool) {
-	// We only encode as nil if a nillable value.
-	// This removes some of the wasted checks for TryDecodeAsNil.
-	// We need to think about this more, to see what happens if omitempty, etc
-	// cause a nil value to be stored when something is expected.
-	// This could happen when decoding from a struct encoded as an array.
-	// For that, decVar should be called with canNil=true, to force true as its value.
-	i := x.varsfx()
-	if !canBeNil {
-		canBeNil = genAnythingCanBeNil || !genIsImmutable(t)
+func (x *genRunner) decVarInitPtr(varname, nilvar string, t reflect.Type, si *structFieldInfo,
+	newbuf, nilbuf *genBuf) (t2 reflect.StructField) {
+	//we must accommodate anonymous fields, where the embedded field is a nil pointer in the value.
+	// t2 = t.FieldByIndex(si.is)
+	t2typ := t
+	varname3 := varname
+	t2kind := t2typ.Kind()
+	var nilbufed bool
+	if si != nil {
+		for ij, ix := range si.is {
+			if uint8(ij) == si.nis {
+				break
+			}
+			for t2typ.Kind() == reflect.Ptr {
+				t2typ = t2typ.Elem()
+			}
+			t2 = t2typ.Field(int(ix))
+			t2typ = t2.Type
+			varname3 = varname3 + "." + t2.Name
+			t2kind = t2typ.Kind()
+			if t2kind != reflect.Ptr {
+				continue
+			}
+			if newbuf != nil {
+				newbuf.f("if %s == nil { %s = new(%s) }\n", varname3, varname3, x.genTypeName(t2typ.Elem()))
+			}
+			if nilbuf != nil {
+				if !nilbufed {
+					nilbuf.s("if true")
+					nilbufed = true
+				}
+				nilbuf.s(" && ").s(varname3).s(" != nil")
+			}
+		}
 	}
-	if canBeNil {
-		x.line("if r.TryDecodeAsNil() {")
-		if decodedNilVarname != "" {
-			x.line(decodedNilVarname + " = true")
-		} else if t.Kind() == reflect.Ptr {
-			x.line("if " + varname + " != nil { ")
-
-			// if varname is a field of a struct (has a dot in it),
-			// then just set it to nil
-			if strings.IndexByte(varname, '.') != -1 {
-				x.line(varname + " = nil")
+	// if t2typ.Kind() == reflect.Ptr {
+	// 	varname3 = varname3 + t2.Name
+	// }
+	if nilbuf != nil {
+		if nilbufed {
+			nilbuf.s(" { ")
+		}
+		if nilvar != "" {
+			nilbuf.s(nilvar).s(" = true")
+		} else if tk := t2typ.Kind(); tk == reflect.Ptr {
+			if strings.IndexByte(varname3, '.') != -1 || strings.IndexByte(varname3, '[') != -1 {
+				nilbuf.s(varname3).s(" = nil")
 			} else {
-				x.line("*" + varname + " = " + x.genZeroValueR(t.Elem()))
+				nilbuf.s("*").s(varname3).s(" = ").s(x.genZeroValueR(t2typ.Elem()))
 			}
-			x.line("}")
 		} else {
-			x.line(varname + " = " + x.genZeroValueR(t))
+			nilbuf.s(varname3).s(" = ").s(x.genZeroValueR(t2typ))
+		}
+		if nilbufed {
+			nilbuf.s("}")
 		}
-		x.line("} else {")
-	} else {
-		x.line("// cannot be nil")
 	}
+	return t2
+}
+
+// decVar takes a variable called varname, of type t
+func (x *genRunner) decVarMain(varname, rand string, t reflect.Type, checkNotNil bool) {
+	// We only encode as nil if a nillable value.
+	// This removes some of the wasted checks for TryDecodeAsNil.
+	// We need to think about this more, to see what happens if omitempty, etc
+	// cause a nil value to be stored when something is expected.
+	// This could happen when decoding from a struct encoded as an array.
+	// For that, decVar should be called with canNil=true, to force true as its value.
+	var varname2 string
 	if t.Kind() != reflect.Ptr {
-		if x.decTryAssignPrimitive(varname, t) {
-			x.line(genTempVarPfx + "v" + i + " := &" + varname)
-			x.dec(genTempVarPfx+"v"+i, t)
+		if t.PkgPath() != "" || !x.decTryAssignPrimitive(varname, t, false) {
+			x.dec(varname, t, false)
+			// TODO: should we create temp var if a struct or slice/map indexing?
+			// if strings.IndexByte(varname, '.') != -1 {
+			// 	x.dec(varname, t, false)
+			// } else {
+			// 	varname2 = genTempVarPfx + "v" + rand
+			// 	x.line(varname2 + " := &" + varname)
+			// 	x.dec(varname2, t, true)
+			// }
 		}
 	} else {
-		x.linef("if %s == nil { %s = new(%s) }", varname, varname, x.genTypeName(t.Elem()))
+		if checkNotNil {
+			x.linef("if %s == nil { %s = new(%s) }", varname, varname, x.genTypeName(t.Elem()))
+		}
 		// Ensure we set underlying ptr to a non-nil value (so we can deref to it later).
 		// There's a chance of a **T in here which is nil.
 		var ptrPfx string
 		for t = t.Elem(); t.Kind() == reflect.Ptr; t = t.Elem() {
 			ptrPfx += "*"
-			x.linef("if %s%s == nil { %s%s = new(%s)}",
-				ptrPfx, varname, ptrPfx, varname, x.genTypeName(t))
-		}
-		// if varname has [ in it, then create temp variable for this ptr thingie
-		if strings.Index(varname, "[") >= 0 {
-			varname2 := genTempVarPfx + "w" + i
-			x.line(varname2 + " := " + varname)
-			varname = varname2
+			if checkNotNil {
+				x.linef("if %s%s == nil { %s%s = new(%s)}",
+					ptrPfx, varname, ptrPfx, varname, x.genTypeName(t))
+			}
 		}
+		// TODO: Should we create temp var if a slice/map indexing?
+		// // if varname has [ in it, then create temp variable for this ptr thingie
+		// if strings.IndexByte(varname, '[') != -1 {
+		// 	varname2 = genTempVarPfx + "w" + rand
+		// 	x.line(varname2 + " := " + varname)
+		// 	varname = varname2
+		// }
 
 		if ptrPfx == "" {
-			x.dec(varname, t)
+			x.dec(varname, t, true)
 		} else {
-			x.line(genTempVarPfx + "z" + i + " := " + ptrPfx + varname)
-			x.dec(genTempVarPfx+"z"+i, t)
+			varname2 = genTempVarPfx + "z" + rand
+			x.line(varname2 + " := " + ptrPfx + varname)
+			x.dec(varname2, t, true)
 		}
+	}
+}
+
+// decVar takes a variable called varname, of type t
+func (x *genRunner) decVar(varname, nilvar string, t reflect.Type, canBeNil, checkNotNil bool) {
+	i := x.varsfx()
+
+	// We only encode as nil if a nillable value.
+	// This removes some of the wasted checks for TryDecodeAsNil.
+	// We need to think about this more, to see what happens if omitempty, etc
+	// cause a nil value to be stored when something is expected.
+	// This could happen when decoding from a struct encoded as an array.
+	// For that, decVar should be called with canNil=true, to force true as its value.
 
+	if !canBeNil {
+		canBeNil = genAnythingCanBeNil || !genIsImmutable(t)
+	}
+
+	if canBeNil {
+		var buf genBuf
+		x.decVarInitPtr(varname, nilvar, t, nil, nil, &buf)
+		x.linef("if r.TryDecodeAsNil() { %s } else {", buf.buf)
+	} else {
+		x.line("// cannot be nil")
 	}
 
+	x.decVarMain(varname, i, t, checkNotNil)
+
 	if canBeNil {
 		x.line("} ")
 	}
 }
 
-// dec will decode a variable (varname) of type ptrTo(t).
+// dec will decode a variable (varname) of type t or ptrTo(t) if isptr==true.
 // t is always a basetype (i.e. not of kind reflect.Ptr).
-func (x *genRunner) dec(varname string, t reflect.Type) {
+func (x *genRunner) dec(varname string, t reflect.Type, isptr bool) {
 	// assumptions:
 	//   - the varname is to a pointer already. No need to take address of it
 	//   - t is always a baseType T (not a *T, etc).
 	rtid := rt2id(t)
-	tptr := reflect.PtrTo(t)
+	ti2 := x.ti.get(rtid, t)
+	// tptr := reflect.PtrTo(t)
 	if x.checkForSelfer(t, varname) {
-		if t.Implements(selferTyp) || tptr.Implements(selferTyp) {
+		if ti2.cs || ti2.csp { // t.Implements(selferTyp) || tptr.Implements(selferTyp) {
 			x.line(varname + ".CodecDecodeSelf(d)")
 			return
 		}
@@ -1198,16 +1309,23 @@ func (x *genRunner) dec(varname string, t reflect.Type) {
 	x.line("if false {")           //start if block
 	defer func() { x.line("}") }() //end if block
 
+	var ptrPfx, addrPfx string
+	if isptr {
+		ptrPfx = "*"
+	} else {
+		addrPfx = "&"
+	}
 	if t == timeTyp {
-		x.linef("} else { *%v = r.DecodeTime()", varname)
+		x.linef("} else { %s%v = r.DecodeTime()", ptrPfx, varname)
 		return
 	}
 	if t == rawTyp {
-		x.linef("} else { *%v = z.DecRaw()", varname)
+		x.linef("} else { %s%v = z.DecRaw()", ptrPfx, varname)
 		return
 	}
+
 	if t == rawExtTyp {
-		x.linef("} else { r.DecodeExt(%v, 0, nil)", varname)
+		x.linef("} else { r.DecodeExt(%s%v, 0, nil)", addrPfx, varname)
 		return
 	}
 
@@ -1219,54 +1337,23 @@ func (x *genRunner) dec(varname string, t reflect.Type) {
 		x.linef("} else if %s := z.Extension(z.I2Rtid(%s)); %s != nil { z.DecExtension(%s, %s) ", yy, varname, yy, varname, yy)
 	}
 
-	if t.Implements(binaryUnmarshalerTyp) || tptr.Implements(binaryUnmarshalerTyp) {
-		x.linef("} else if z.DecBinary() { z.DecBinaryUnmarshal(%v) ", varname)
+	if ti2.bu || ti2.bup { // t.Implements(binaryUnmarshalerTyp) || tptr.Implements(binaryUnmarshalerTyp) {
+		x.linef("} else if z.DecBinary() { z.DecBinaryUnmarshal(%s%v) ", addrPfx, varname)
 	}
-	if t.Implements(jsonUnmarshalerTyp) || tptr.Implements(jsonUnmarshalerTyp) {
-		x.linef("} else if !z.DecBinary() && z.IsJSONHandle() { z.DecJSONUnmarshal(%v)", varname)
-	} else if t.Implements(textUnmarshalerTyp) || tptr.Implements(textUnmarshalerTyp) {
-		x.linef("} else if !z.DecBinary() { z.DecTextUnmarshal(%v)", varname)
+	if ti2.ju || ti2.jup { // t.Implements(jsonUnmarshalerTyp) || tptr.Implements(jsonUnmarshalerTyp) {
+		x.linef("} else if !z.DecBinary() && z.IsJSONHandle() { z.DecJSONUnmarshal(%s%v)", addrPfx, varname)
+	} else if ti2.tu || ti2.tup { // t.Implements(textUnmarshalerTyp) || tptr.Implements(textUnmarshalerTyp) {
+		x.linef("} else if !z.DecBinary() { z.DecTextUnmarshal(%s%v)", addrPfx, varname)
 	}
 
 	x.line("} else {")
 
-	// Since these are pointers, we cannot share, and have to use them one by one
+	if x.decTryAssignPrimitive(varname, t, isptr) {
+		return
+	}
 	switch t.Kind() {
-	case reflect.Int:
-		x.line("*((*int)(" + varname + ")) = int(z.C.IntV(r.DecodeInt64(), codecSelferBitsize" + x.xs + "))")
-	case reflect.Int8:
-		x.line("*((*int8)(" + varname + ")) = int8(z.C.IntV(r.DecodeInt64(), 8))")
-	case reflect.Int16:
-		x.line("*((*int16)(" + varname + ")) = int16(z.C.IntV(r.DecodeInt64(), 16))")
-	case reflect.Int32:
-		x.line("*((*int32)(" + varname + ")) = int32(z.C.IntV(r.DecodeInt64(), 32))")
-	case reflect.Int64:
-		x.line("*((*int64)(" + varname + ")) = int64(r.DecodeInt64())")
-
-	case reflect.Uint:
-		x.line("*((*uint)(" + varname + ")) = uint(z.C.UintV(r.DecodeUint64(), codecSelferBitsize" + x.xs + "))")
-	case reflect.Uint8:
-		x.line("*((*uint8)(" + varname + ")) = uint8(z.C.UintV(r.DecodeUint64(), 8))")
-	case reflect.Uint16:
-		x.line("*((*uint16)(" + varname + ")) = uint16(z.C.UintV(r.DecodeUint64(), 16))")
-	case reflect.Uint32:
-		x.line("*((*uint32)(" + varname + ")) = uint32(z.C.UintV(r.DecodeUint64(), 32))")
-	case reflect.Uint64:
-		x.line("*((*uint64)(" + varname + ")) = uint64(r.DecodeUint64())")
-	case reflect.Uintptr:
-		x.line("*((*uintptr)(" + varname + ")) = uintptr(z.C.UintV(r.DecodeUint64(), codecSelferBitsize" + x.xs + "))")
-
-	case reflect.Float32:
-		x.line("*((*float32)(" + varname + ")) = float32(r.DecodeFloat32As64())")
-	case reflect.Float64:
-		x.line("*((*float64)(" + varname + ")) = r.DecodeFloat64()")
-
-	case reflect.Bool:
-		x.line("*((*bool)(" + varname + ")) = r.DecodeBool()")
-	case reflect.String:
-		x.line("*((*string)(" + varname + ")) = r.DecodeString()")
 	case reflect.Array, reflect.Chan:
-		x.xtraSM(varname, false, t)
+		x.xtraSM(varname, t, false, isptr)
 	case reflect.Slice:
 		// if a []uint8, call dedicated function
 		// if a known fastpath slice, call dedicated function
@@ -1274,12 +1361,15 @@ func (x *genRunner) dec(varname string, t reflect.Type) {
 		// - if elements are primitives or Selfers, call dedicated function on each member.
 		// - else call Encoder.encode(XXX) on it.
 		if rtid == uint8SliceTypId {
-			x.line("*" + varname + " = r.DecodeBytes(*(*[]byte)(" + varname + "), false)")
+			x.linef("%s%s = r.DecodeBytes(%s(%s[]byte)(%s), false)",
+				ptrPfx, varname, ptrPfx, ptrPfx, varname)
+			// x.line("*" + varname + " = r.DecodeBytes(*(*[]byte)(" + varname + "), false)")
 		} else if fastpathAV.index(rtid) != -1 {
 			g := x.newGenV(t)
-			x.line("z.F." + g.MethodNamePfx("Dec", false) + "X(" + varname + ", d)")
+			x.linef("z.F.%sX(%s%s, d)", g.MethodNamePfx("Dec", false), addrPfx, varname)
+			// x.line("z.F." + g.MethodNamePfx("Dec", false) + "X(" + varname + ", d)")
 		} else {
-			x.xtraSM(varname, false, t)
+			x.xtraSM(varname, t, false, isptr)
 			// x.decListFallback(varname, rtid, false, t)
 		}
 	case reflect.Map:
@@ -1289,74 +1379,79 @@ func (x *genRunner) dec(varname string, t reflect.Type) {
 		// - else call Encoder.encode(XXX) on it.
 		if fastpathAV.index(rtid) != -1 {
 			g := x.newGenV(t)
-			x.line("z.F." + g.MethodNamePfx("Dec", false) + "X(" + varname + ", d)")
+			x.linef("z.F.%sX(%s%s, d)", g.MethodNamePfx("Dec", false), addrPfx, varname)
+			// x.line("z.F." + g.MethodNamePfx("Dec", false) + "X(" + varname + ", d)")
 		} else {
-			x.xtraSM(varname, false, t)
+			x.xtraSM(varname, t, false, isptr)
 			// x.decMapFallback(varname, rtid, t)
 		}
 	case reflect.Struct:
 		if inlist {
-			x.decStruct(varname, rtid, t)
+			if isptr {
+				x.decStruct(varname, rtid, t)
+			} else {
+				// TODO: create temp variable, and call pass to decStruct
+
+			}
 		} else {
 			// delete(x.td, rtid)
-			x.line("z.DecFallback(" + varname + ", false)")
+			x.line("z.DecFallback(" + addrPfx + varname + ", false)")
 		}
 	default:
 		if rtidAdded {
 			delete(x.te, rtid)
 		}
-		x.line("z.DecFallback(" + varname + ", true)")
+		x.line("z.DecFallback(" + addrPfx + varname + ", true)")
 	}
 }
 
-func (x *genRunner) decTryAssignPrimitive(varname string, t reflect.Type) (tryAsPtr bool) {
+func (x *genRunner) decTryAssignPrimitive(varname string, t reflect.Type, isptr bool) (done bool) {
 	// This should only be used for exact primitives (ie un-named types).
 	// Named types may be implementations of Selfer, Unmarshaler, etc.
 	// They should be handled by dec(...)
 
-	if t.Name() != "" {
-		tryAsPtr = true
-		return
+	var ptr string
+	if isptr {
+		ptr = "*"
 	}
-
 	switch t.Kind() {
 	case reflect.Int:
-		x.linef("%s = z.C.IntV(r.DecodeInt64(), codecSelferBitsize%s)", varname, x.xs)
+		x.linef("%s%s = (%s)(z.C.IntV(r.DecodeInt64(), codecSelferBitsize%s))", ptr, varname, x.genTypeName(t), x.xs)
 	case reflect.Int8:
-		x.linef("%s = z.C.IntV(r.DecodeInt64(), 8)", varname)
+		x.linef("%s%s = (%s)(z.C.IntV(r.DecodeInt64(), 8))", ptr, varname, x.genTypeName(t))
 	case reflect.Int16:
-		x.linef("%s = z.C.IntV(r.DecodeInt64(), 16)", varname)
+		x.linef("%s%s = (%s)(z.C.IntV(r.DecodeInt64(), 16))", ptr, varname, x.genTypeName(t))
 	case reflect.Int32:
-		x.linef("%s = z.C.IntV(r.DecodeInt64(), 32)", varname)
+		x.linef("%s%s = (%s)(z.C.IntV(r.DecodeInt64(), 32))", ptr, varname, x.genTypeName(t))
 	case reflect.Int64:
-		x.linef("%s = r.DecodeInt64()", varname)
+		x.linef("%s%s = (%s)(r.DecodeInt64())", ptr, varname, x.genTypeName(t))
 
 	case reflect.Uint:
-		x.linef("%s = z.C.UintV(r.DecodeUint64(), codecSelferBitsize%s)", varname, x.xs)
+		x.linef("%s%s = (%s)(z.C.UintV(r.DecodeUint64(), codecSelferBitsize%s))", ptr, varname, x.genTypeName(t), x.xs)
 	case reflect.Uint8:
-		x.linef("%s = z.C.UintV(r.DecodeUint64(), 8)", varname)
+		x.linef("%s%s = (%s)(z.C.UintV(r.DecodeUint64(), 8))", ptr, varname, x.genTypeName(t))
 	case reflect.Uint16:
-		x.linef("%s = z.C.UintV(r.DecodeUint64(), 16)", varname)
+		x.linef("%s%s = (%s)(z.C.UintV(r.DecodeUint64(), 16))", ptr, varname, x.genTypeName(t))
 	case reflect.Uint32:
-		x.linef("%s = z.C.UintV(r.DecodeUint64(), 32)", varname)
+		x.linef("%s%s = (%s)(z.C.UintV(r.DecodeUint64(), 32))", ptr, varname, x.genTypeName(t))
 	case reflect.Uint64:
-		x.linef("%s = r.DecodeUint64()", varname)
+		x.linef("%s%s = (%s)(r.DecodeUint64())", ptr, varname, x.genTypeName(t))
 	case reflect.Uintptr:
-		x.linef("%s = z.C.UintV(r.DecodeUint64(), codecSelferBitsize%s)", varname, x.xs)
+		x.linef("%s%s = (%s)(z.C.UintV(r.DecodeUint64(), codecSelferBitsize%s))", ptr, varname, x.genTypeName(t), x.xs)
 
 	case reflect.Float32:
-		x.linef("%s = r.DecodeFloat32As64()", varname)
+		x.linef("%s%s = (%s)(r.DecodeFloat32As64())", ptr, varname, x.genTypeName(t))
 	case reflect.Float64:
-		x.linef("%s = r.DecodeFloat64()", varname)
+		x.linef("%s%s = (%s)(r.DecodeFloat64())", ptr, varname, x.genTypeName(t))
 
 	case reflect.Bool:
-		x.linef("%s = r.DecodeBool()", varname)
+		x.linef("%s%s = (%s)(r.DecodeBool())", ptr, varname, x.genTypeName(t))
 	case reflect.String:
-		x.linef("%s = r.DecodeString()", varname)
+		x.linef("%s%s = (%s)(r.DecodeString())", ptr, varname, x.genTypeName(t))
 	default:
-		tryAsPtr = true
+		return false
 	}
-	return
+	return true
 }
 
 func (x *genRunner) decListFallback(varname string, rtid uintptr, t reflect.Type) {
@@ -1383,7 +1478,7 @@ func (x *genRunner) decListFallback(varname string, rtid uintptr, t reflect.Type
 	funcs := make(template.FuncMap)
 
 	funcs["decLineVar"] = func(varname string) string {
-		x.decVar(varname, "", telem, false)
+		x.decVar(varname, "", telem, false, true)
 		return ""
 	}
 	// funcs["decLine"] = func(pfx string) string {
@@ -1445,11 +1540,11 @@ func (x *genRunner) decMapFallback(varname string, rtid uintptr, t reflect.Type)
 		return telem.Kind() == reflect.Interface
 	}
 	funcs["decLineVarK"] = func(varname string) string {
-		x.decVar(varname, "", tkey, false)
+		x.decVar(varname, "", tkey, false, true)
 		return ""
 	}
 	funcs["decLineVar"] = func(varname, decodedNilVarname string) string {
-		x.decVar(varname, decodedNilVarname, telem, false)
+		x.decVar(varname, decodedNilVarname, telem, false, true)
 		return ""
 	}
 	// funcs["decLineK"] = func(pfx string) string {
@@ -1477,30 +1572,15 @@ func (x *genRunner) decStructMapSwitch(kName string, varname string, rtid uintpt
 	ti := x.ti.get(rtid, t)
 	tisfi := ti.sfiSrc // always use sequence from file. decStruct expects same thing.
 	x.line("switch (" + kName + ") {")
+	var newbuf, nilbuf genBuf
 	for _, si := range tisfi {
 		x.line("case \"" + si.encName + "\":")
-		var t2 reflect.StructField
-		{
-			//we must accommodate anonymous fields, where the embedded field is a nil pointer in the value.
-			// t2 = t.FieldByIndex(si.is)
-			t2typ := t
-			varname3 := varname
-			for ij, ix := range si.is {
-				if uint8(ij) == si.nis {
-					break
-				}
-				for t2typ.Kind() == reflect.Ptr {
-					t2typ = t2typ.Elem()
-				}
-				t2 = t2typ.Field(int(ix))
-				t2typ = t2.Type
-				varname3 = varname3 + "." + t2.Name
-				if t2typ.Kind() == reflect.Ptr {
-					x.linef("if %s == nil { %s = new(%s) }", varname3, varname3, x.genTypeName(t2typ.Elem()))
-				}
-			}
-		}
-		x.decVar(varname+"."+t2.Name, "", t2.Type, false)
+		newbuf.reset()
+		nilbuf.reset()
+		t2 := x.decVarInitPtr(varname, "", t, si, &newbuf, &nilbuf)
+		x.linef("if r.TryDecodeAsNil() { %s } else { %s", nilbuf.buf, newbuf.buf)
+		x.decVarMain(varname+"."+t2.Name, x.varsfx(), t2.Type, false)
+		x.line("}")
 	}
 	x.line("default:")
 	// pass the slice here, so that the string will not escape, and maybe save allocation
@@ -1555,35 +1635,19 @@ func (x *genRunner) decStructArray(varname, lenvarname, breakString string, rtid
 	x.linef("var %sj%s int", tpfx, i)
 	x.linef("var %sb%s bool", tpfx, i)                        // break
 	x.linef("var %shl%s bool = %s >= 0", tpfx, i, lenvarname) // has length
+	var newbuf, nilbuf genBuf
 	for _, si := range tisfi {
-		var t2 reflect.StructField
-		{
-			//we must accommodate anonymous fields, where the embedded field is a nil pointer in the value.
-			// t2 = t.FieldByIndex(si.is)
-			t2typ := t
-			varname3 := varname
-			for ij, ix := range si.is {
-				if uint8(ij) == si.nis {
-					break
-				}
-				for t2typ.Kind() == reflect.Ptr {
-					t2typ = t2typ.Elem()
-				}
-				t2 = t2typ.Field(int(ix))
-				t2typ = t2.Type
-				varname3 = varname3 + "." + t2.Name
-				if t2typ.Kind() == reflect.Ptr {
-					x.linef("if %s == nil { %s = new(%s) }", varname3, varname3, x.genTypeName(t2typ.Elem()))
-				}
-			}
-		}
-
 		x.linef("%sj%s++; if %shl%s { %sb%s = %sj%s > %s } else { %sb%s = r.CheckBreak() }",
 			tpfx, i, tpfx, i, tpfx, i,
 			tpfx, i, lenvarname, tpfx, i)
 		x.linef("if %sb%s { r.ReadArrayEnd(); %s }", tpfx, i, breakString)
 		x.line("r.ReadArrayElem()")
-		x.decVar(varname+"."+t2.Name, "", t2.Type, true)
+		newbuf.reset()
+		nilbuf.reset()
+		t2 := x.decVarInitPtr(varname, "", t, si, &newbuf, &nilbuf)
+		x.linef("if r.TryDecodeAsNil() { %s } else { %s", nilbuf.buf, newbuf.buf)
+		x.decVarMain(varname+"."+t2.Name, x.varsfx(), t2.Type, false)
+		x.line("}")
 	}
 	// read remaining values and throw away.
 	x.line("for {")
@@ -1599,6 +1663,7 @@ func (x *genRunner) decStructArray(varname, lenvarname, breakString string, rtid
 
 func (x *genRunner) decStruct(varname string, rtid uintptr, t reflect.Type) {
 	// if container is map
+	// varname MUST be a ptr.
 	i := x.varsfx()
 	x.linef("%sct%s := r.ContainerType()", genTempVarPfx, i)
 	x.linef("if %sct%s == codecSelferValueTypeMap%s {", genTempVarPfx, i, x.xs)
@@ -1607,12 +1672,12 @@ func (x *genRunner) decStruct(varname string, rtid uintptr, t reflect.Type) {
 	x.line("r.ReadMapEnd()")
 	if genUseOneFunctionForDecStructMap {
 		x.line("} else { ")
-		x.linef("x.codecDecodeSelfFromMap(%sl%s, d)", genTempVarPfx, i)
+		x.linef("%s.codecDecodeSelfFromMap(%sl%s, d)", varname, genTempVarPfx, i)
 	} else {
 		x.line("} else if " + genTempVarPfx + "l" + i + " > 0 { ")
-		x.line("x.codecDecodeSelfFromMapLenPrefix(" + genTempVarPfx + "l" + i + ", d)")
+		x.line(varname + ".codecDecodeSelfFromMapLenPrefix(" + genTempVarPfx + "l" + i + ", d)")
 		x.line("} else {")
-		x.line("x.codecDecodeSelfFromMapCheckBreak(" + genTempVarPfx + "l" + i + ", d)")
+		x.line(varname + ".codecDecodeSelfFromMapCheckBreak(" + genTempVarPfx + "l" + i + ", d)")
 	}
 	x.line("}")
 
@@ -1622,7 +1687,7 @@ func (x *genRunner) decStruct(varname string, rtid uintptr, t reflect.Type) {
 	x.linef("if %sl%s == 0 {", genTempVarPfx, i)
 	x.line("r.ReadArrayEnd()")
 	x.line("} else { ")
-	x.linef("x.codecDecodeSelfFromArray(%sl%s, d)", genTempVarPfx, i)
+	x.linef("%s.codecDecodeSelfFromArray(%sl%s, d)", varname, genTempVarPfx, i)
 	x.line("}")
 	// else panic
 	x.line("} else { ")

+ 103 - 18
codec/helper.go

@@ -996,6 +996,14 @@ func baseStructRv(v reflect.Value, update bool) (v2 reflect.Value, valid bool) {
 	return v, true
 }
 
+type typeInfoFlag uint8
+
+const (
+	typeInfoFlagComparable = 1 << iota
+	typeInfoFlagIsZeroer
+	typeInfoFlagIsZeroerPtr
+)
+
 // typeInfo keeps information about each (non-ptr) type referenced in the encode/decode sequence.
 //
 // During an encode/decode sequence, we work as below:
@@ -1005,25 +1013,31 @@ func baseStructRv(v reflect.Value, update bool) (v2 reflect.Value, valid bool) {
 //   - If type is text(M/Unm)arshaler, call Text(M/Unm)arshal method
 //   - Else decode appropriately based on the reflect.Kind
 type typeInfo struct {
-	sfiSort []*structFieldInfo // sorted. Used when enc/dec struct to map.
-	sfiSrc  []*structFieldInfo // unsorted. Used when enc/dec struct to array.
 	rt      reflect.Type
-
-	// ---- cpu cache line boundary?
-	// sfis         []structFieldInfo // all sfi, in src order, as created.
-	sfiNamesSort []byte // all names, with indexes into the sfiSort
+	elem    reflect.Type
+	pkgpath string
 
 	rtid uintptr
 	// rv0  reflect.Value // saved zero value, used if immutableKind
 
 	numMeth uint16 // number of methods
+	kind    uint8
+	chandir uint8
 
-	// comparable   bool      // true if a struct, and is comparable
 	anyOmitEmpty bool      // true if a struct, and any of the fields are tagged "omitempty"
 	toArray      bool      // whether this (struct) type should be encoded as an array
 	keyType      valueType // if struct, how is the field name stored in a stream? default is string
+	mbs          bool      // base type (T or *T) is a MapBySlice
 
-	mbs bool // base type (T or *T) is a MapBySlice
+	// ---- cpu cache line boundary?
+	sfiSort []*structFieldInfo // sorted. Used when enc/dec struct to map.
+	sfiSrc  []*structFieldInfo // unsorted. Used when enc/dec struct to array.
+
+	key reflect.Type
+
+	// ---- cpu cache line boundary?
+	// sfis         []structFieldInfo // all sfi, in src order, as created.
+	sfiNamesSort []byte // all names, with indexes into the sfiSort
 
 	// format of marshal type fields below: [btj][mu]p? OR csp?
 
@@ -1035,6 +1049,7 @@ type typeInfo struct {
 	tmp bool // *T is a textMarshaler
 	tu  bool // T is a textUnmarshaler
 	tup bool // *T is a textUnmarshaler
+
 	jm  bool // T is a jsonMarshaler
 	jmp bool // *T is a jsonMarshaler
 	ju  bool // T is a jsonUnmarshaler
@@ -1042,7 +1057,15 @@ type typeInfo struct {
 	cs  bool // T is a Selfer
 	csp bool // *T is a Selfer
 
-	_ [1]uint64 // padding
+	// other flags, with individual bits representing if set.
+	flags typeInfoFlag
+
+	// _ [2]byte   // padding
+	_ [3]uint64 // padding
+}
+
+func (ti *typeInfo) isFlag(f typeInfoFlag) bool {
+	return ti.flags&f != 0
 }
 
 func (ti *typeInfo) indexForEncName(name []byte) (index int16) {
@@ -1139,7 +1162,7 @@ func (x *TypeInfos) get(rtid uintptr, rt reflect.Type) (pti *typeInfo) {
 
 	// do not hold lock while computing this.
 	// it may lead to duplication, but that's ok.
-	ti := typeInfo{rt: rt, rtid: rtid}
+	ti := typeInfo{rt: rt, rtid: rtid, kind: uint8(rk), pkgpath: rt.PkgPath()}
 	// ti.rv0 = reflect.Zero(rt)
 
 	// ti.comparable = rt.Comparable()
@@ -1152,11 +1175,20 @@ func (x *TypeInfos) get(rtid uintptr, rt reflect.Type) (pti *typeInfo) {
 	ti.jm, ti.jmp = implIntf(rt, jsonMarshalerTyp)
 	ti.ju, ti.jup = implIntf(rt, jsonUnmarshalerTyp)
 	ti.cs, ti.csp = implIntf(rt, selferTyp)
-	if rt.Kind() == reflect.Slice {
-		ti.mbs, _ = implIntf(rt, mapBySliceTyp)
+
+	b1, b2 := implIntf(rt, iszeroTyp)
+	if b1 {
+		ti.flags |= typeInfoFlagIsZeroer
+	}
+	if b2 {
+		ti.flags |= typeInfoFlagIsZeroerPtr
+	}
+	if rt.Comparable() {
+		ti.flags |= typeInfoFlagComparable
 	}
 
-	if rk == reflect.Struct {
+	switch rk {
+	case reflect.Struct:
 		var omitEmpty bool
 		if f, ok := rt.FieldByName(structInfoFieldName); ok {
 			ti.toArray, omitEmpty, ti.keyType = parseStructInfo(x.structTag(f.Tag))
@@ -1172,6 +1204,17 @@ func (x *TypeInfos) get(rtid uintptr, rt reflect.Type) (pti *typeInfo) {
 		// ti.sfis = vv.sfis
 		ti.sfiSrc, ti.sfiSort, ti.sfiNamesSort, ti.anyOmitEmpty = rgetResolveSFI(rt, vv.sfis, pv)
 		pp.Put(pi)
+	case reflect.Map:
+		ti.elem = rt.Elem()
+		ti.key = rt.Key()
+	case reflect.Slice:
+		ti.mbs, _ = implIntf(rt, mapBySliceTyp)
+		ti.elem = rt.Elem()
+	case reflect.Chan:
+		ti.elem = rt.Elem()
+		ti.chandir = uint8(rt.ChanDir())
+	case reflect.Array, reflect.Ptr:
+		ti.elem = rt.Elem()
 	}
 	// sfi = sfiSrc
 
@@ -1445,6 +1488,48 @@ func implIntf(rt, iTyp reflect.Type) (base bool, indir bool) {
 	return rt.Implements(iTyp), reflect.PtrTo(rt).Implements(iTyp)
 }
 
+// isEmptyStruct is only called from isEmptyValue, and checks if a struct is empty:
+//    - does it implement IsZero() bool
+//    - is it comparable, and can i compare directly using ==
+//    - if checkStruct, then walk through the encodable fields
+//      and check if they are empty or not.
+func isEmptyStruct(v reflect.Value, tinfos *TypeInfos, deref, checkStruct bool) bool {
+	// v is a struct kind - no need to check again.
+	// We only check isZero on a struct kind, to reduce the amount of times
+	// that we lookup the rtid and typeInfo for each type as we walk the tree.
+
+	vt := v.Type()
+	rtid := rt2id(vt)
+	if tinfos == nil {
+		tinfos = defTypeInfos
+	}
+	ti := tinfos.get(rtid, vt)
+	if ti.rtid == timeTypId {
+		return rv2i(v).(time.Time).IsZero()
+	}
+	if ti.isFlag(typeInfoFlagIsZeroerPtr) && v.CanAddr() {
+		return rv2i(v.Addr()).(isZeroer).IsZero()
+	}
+	if ti.isFlag(typeInfoFlagIsZeroer) {
+		return rv2i(v).(isZeroer).IsZero()
+	}
+	if ti.isFlag(typeInfoFlagComparable) {
+		return rv2i(v) == rv2i(reflect.Zero(vt))
+	}
+	if !checkStruct {
+		return false
+	}
+	// We only care about what we can encode/decode,
+	// so that is what we use to check omitEmpty.
+	for _, si := range ti.sfiSrc {
+		sfv, valid := si.field(v, false)
+		if valid && !isEmptyValue(sfv, tinfos, deref, checkStruct) {
+			return false
+		}
+	}
+	return true
+}
+
 // func roundFloat(x float64) float64 {
 // 	t := math.Trunc(x)
 // 	if math.Abs(x-t) >= 0.5 {
@@ -1591,7 +1676,7 @@ func (c *codecFner) get(rt reflect.Type, checkFastpath, checkCodecSelfer bool) (
 		fi.ti = ti
 	}
 
-	rk := rt.Kind()
+	rk := reflect.Kind(ti.kind)
 
 	if checkCodecSelfer && (ti.cs || ti.csp) {
 		fn.fe = (*Encoder).selferMarshal
@@ -1641,7 +1726,7 @@ func (c *codecFner) get(rt reflect.Type, checkFastpath, checkCodecSelfer bool) (
 		fi.addrE = ti.tmp
 	} else {
 		if fastpathEnabled && checkFastpath && (rk == reflect.Map || rk == reflect.Slice) {
-			if rt.PkgPath() == "" { // un-named slice or map
+			if ti.pkgpath == "" { // un-named slice or map
 				if idx := fastpathAV.index(rtid); idx != -1 {
 					fn.fe = fastpathAV[idx].encfn
 					fn.fd = fastpathAV[idx].decfn
@@ -1652,9 +1737,9 @@ func (c *codecFner) get(rt reflect.Type, checkFastpath, checkCodecSelfer bool) (
 				// use mapping for underlying type if there
 				var rtu reflect.Type
 				if rk == reflect.Map {
-					rtu = reflect.MapOf(rt.Key(), rt.Elem())
+					rtu = reflect.MapOf(ti.key, ti.elem)
 				} else {
-					rtu = reflect.SliceOf(rt.Elem())
+					rtu = reflect.SliceOf(ti.elem)
 				}
 				rtuid := rt2id(rtu)
 				if idx := fastpathAV.index(rtuid); idx != -1 {
@@ -1739,7 +1824,7 @@ func (c *codecFner) get(rt reflect.Type, checkFastpath, checkCodecSelfer bool) (
 				fn.fe = (*Encoder).kSlice
 				fi.addrF = false
 				fi.addrD = false
-				rt2 := reflect.SliceOf(rt.Elem())
+				rt2 := reflect.SliceOf(ti.elem)
 				fn.fd = func(d *Decoder, xf *codecFnInfo, xrv reflect.Value) {
 					d.cfer().get(rt2, true, false).fd(d, xf, xrv.Slice(0, xrv.Len()))
 				}

+ 0 - 59
codec/helper_internal.go

@@ -6,65 +6,6 @@ package codec
 // All non-std package dependencies live in this file,
 // so porting to different environment is easy (just update functions).
 
-import (
-	"reflect"
-	"time"
-)
-
-func hIsEmptyValue(v reflect.Value, deref, checkStruct bool) bool {
-	if !v.IsValid() {
-		return true
-	}
-	vt := v.Type()
-	if vt.Implements(iszeroTyp) {
-		return rv2i(v).(isZeroer).IsZero()
-	}
-	switch v.Kind() {
-	case reflect.Invalid:
-		return true
-	case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
-		return v.Len() == 0
-	case reflect.Bool:
-		return !v.Bool()
-	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
-		return v.Int() == 0
-	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
-		return v.Uint() == 0
-	case reflect.Float32, reflect.Float64:
-		return v.Float() == 0
-	case reflect.Interface, reflect.Ptr:
-		if deref {
-			if v.IsNil() {
-				return true
-			}
-			return hIsEmptyValue(v.Elem(), deref, checkStruct)
-		}
-		return v.IsNil()
-	case reflect.Struct:
-		if rv2rtid(v) == timeTypId {
-			return rv2i(v).(time.Time).IsZero()
-		}
-		if !checkStruct {
-			return false
-		}
-		if vt.Comparable() {
-			return v.Interface() == reflect.Zero(vt).Interface()
-		}
-		// return true if all fields are empty. else return false.
-		for i, n := 0, v.NumField(); i < n; i++ {
-			if !hIsEmptyValue(v.Field(i), deref, checkStruct) {
-				return false
-			}
-		}
-		return true
-	}
-	return false
-}
-
-func isEmptyValue(v reflect.Value, deref, checkStruct bool) bool {
-	return hIsEmptyValue(v, deref, checkStruct)
-}
-
 func pruneSignExt(v []byte, pos bool) (n int) {
 	if len(v) < 2 {
 	} else if pos && v[0] == 0 {

+ 30 - 0
codec/helper_not_unsafe.go

@@ -55,6 +55,36 @@ func i2rtid(i interface{}) uintptr {
 	return reflect.ValueOf(reflect.TypeOf(i)).Pointer()
 }
 
+// --------------------------
+
+func isEmptyValue(v reflect.Value, tinfos *TypeInfos, deref, checkStruct bool) bool {
+	switch v.Kind() {
+	case reflect.Invalid:
+		return true
+	case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
+		return v.Len() == 0
+	case reflect.Bool:
+		return !v.Bool()
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		return v.Int() == 0
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+		return v.Uint() == 0
+	case reflect.Float32, reflect.Float64:
+		return v.Float() == 0
+	case reflect.Interface, reflect.Ptr:
+		if deref {
+			if v.IsNil() {
+				return true
+			}
+			return isEmptyValue(v.Elem(), tinfos, deref, checkStruct)
+		}
+		return v.IsNil()
+	case reflect.Struct:
+		return isEmptyStruct(v, tinfos, deref, checkStruct)
+	}
+	return false
+}
+
 // --------------------------
 // type ptrToRvMap struct{}
 

+ 68 - 0
codec/helper_unsafe.go

@@ -107,6 +107,74 @@ func i2rtid(i interface{}) uintptr {
 
 // --------------------------
 
+func isEmptyValue(v reflect.Value, tinfos *TypeInfos, deref, checkStruct bool) bool {
+	urv := (*unsafeReflectValue)(unsafe.Pointer(&v))
+	if urv.flag == 0 {
+		return true
+	}
+	switch v.Kind() {
+	case reflect.Invalid:
+		return true
+	case reflect.String:
+		return (*unsafeString)(urv.ptr).Len == 0
+	case reflect.Slice:
+		return (*unsafeSlice)(urv.ptr).Len == 0
+	case reflect.Bool:
+		return !*(*bool)(urv.ptr)
+	case reflect.Int:
+		return *(*int)(urv.ptr) == 0
+	case reflect.Int8:
+		return *(*int8)(urv.ptr) == 0
+	case reflect.Int16:
+		return *(*int16)(urv.ptr) == 0
+	case reflect.Int32:
+		return *(*int32)(urv.ptr) == 0
+	case reflect.Int64:
+		return *(*int64)(urv.ptr) == 0
+	case reflect.Uint:
+		return *(*uint)(urv.ptr) == 0
+	case reflect.Uint8:
+		return *(*uint8)(urv.ptr) == 0
+	case reflect.Uint16:
+		return *(*uint16)(urv.ptr) == 0
+	case reflect.Uint32:
+		return *(*uint32)(urv.ptr) == 0
+	case reflect.Uint64:
+		return *(*uint64)(urv.ptr) == 0
+	case reflect.Uintptr:
+		return *(*uintptr)(urv.ptr) == 0
+	case reflect.Float32:
+		return *(*float32)(urv.ptr) == 0
+	case reflect.Float64:
+		return *(*float64)(urv.ptr) == 0
+	case reflect.Interface:
+		isnil := urv.ptr == nil || *(*unsafe.Pointer)(urv.ptr) == nil
+		if deref {
+			if isnil {
+				return true
+			}
+			return isEmptyValue(v.Elem(), tinfos, deref, checkStruct)
+		}
+		return isnil
+	case reflect.Ptr:
+		isnil := urv.ptr == nil
+		if deref {
+			if isnil {
+				return true
+			}
+			return isEmptyValue(v.Elem(), tinfos, deref, checkStruct)
+		}
+		return isnil
+	case reflect.Struct:
+		return isEmptyStruct(v, tinfos, deref, checkStruct)
+	case reflect.Map, reflect.Array, reflect.Chan:
+		return v.Len() == 0
+	}
+	return false
+}
+
+// --------------------------
+
 type atomicTypeInfoSlice struct { // expected to be 2 words
 	v unsafe.Pointer
 	_ [8]byte // padding

File diff suppressed because it is too large
+ 120 - 270
codec/mammoth2_codecgen_generated_test.go


Some files were not shown because too many files changed in this diff