diff --git a/frugal.go b/frugal.go index 6cdc1e7..7f26996 100644 --- a/frugal.go +++ b/frugal.go @@ -17,6 +17,8 @@ package frugal import ( + "fmt" + "github.com/cloudwego/frugal/internal/opts" "github.com/cloudwego/frugal/internal/reflect" "github.com/cloudwego/gopkg/protocol/thrift" @@ -34,7 +36,13 @@ func EncodedSize(val interface{}) int { // buf must be large enough to contain the entire serialization result. func EncodeObject(buf []byte, w thrift.NocopyWriter, val interface{}) (int, error) { if !jit || opts.NoJIT { - return reflect.Encode(buf, val) // TODO: impl thrift.NocopyWriter + ret, err := reflect.Append(buf[:0], val) + if len(ret) > len(buf) { + return 0, fmt.Errorf("index out of range [%d] with length %d.\n"+ + "Please make sure the input will not be changed after calling EncodedSize or during EncodeObject(concurrency issues).", + len(ret), len(buf)) + } + return len(ret), err } return jitEncodeObject(buf, w, val) } diff --git a/go.mod b/go.mod index 4e2f3ca..1f9dae0 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/cloudwego/frugal -go 1.17 +go 1.18 require ( github.com/cloudwego/gopkg v0.1.2 diff --git a/internal/reflect/append.go b/internal/reflect/append.go new file mode 100644 index 0000000..ca33c4d --- /dev/null +++ b/internal/reflect/append.go @@ -0,0 +1,105 @@ +/* + * Copyright 2024 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package reflect + +import "unsafe" + +func appendStruct(t *tType, b []byte, base unsafe.Pointer) ([]byte, error) { + sd := t.Sd + if base == nil { + return append(b, byte(tSTOP)), nil + } + var err error + for _, f := range sd.fields { + t := f.Type + p := unsafe.Add(base, f.Offset) + if f.CanSkipEncodeIfNil && *(*unsafe.Pointer)(p) == nil { + continue + } + if f.CanSkipIfDefault && t.Equal(f.Default, p) { + continue + } + + // field header + b = append(b, byte(t.WT), byte(f.ID>>8), byte(f.ID)) + + // field value + // the following code should be the same as func `appendAny` + // manually copy here for inlining: + + if t.IsPointer { + p = *(*unsafe.Pointer)(p) + } + if t.SimpleType { // fast path + switch t.T { + case tBYTE, tBOOL: + b = append(b, *(*byte)(p)) // for tBOOL, true -> 1, false -> 0 + case tI16: + b = appendUint16(b, *((*uint16)(p))) + case tI32: + b = appendUint32(b, *((*uint32)(p))) + case tENUM: + b = appendUint32(b, uint32(*((*int64)(p)))) + case tI64, tDOUBLE: + b = appendUint64(b, *((*uint64)(p))) + case tSTRING: + s := *((*string)(p)) + b = appendUint32(b, uint32(len(s))) + b = append(b, s...) + } + } else { + b, err = t.AppendFunc(t, b, p) + if err != nil { + return b, withFieldErr(err, sd, f) + } + } + } + if sd.hasUnknownFields { + xb := *(*[]byte)(unsafe.Add(base, sd.unknownFieldsOffset)) + if len(xb) > 0 { + b = append(b, xb...) + } + } + return append(b, byte(tSTOP)), nil +} + +func appendAny(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + if t.IsPointer { + p = *(*unsafe.Pointer)(p) + } + if t.SimpleType { + switch t.T { + case tBYTE, tBOOL: + b = append(b, *(*byte)(p)) // for tBOOL, true -> 1, false -> 0 + case tI16: + b = appendUint16(b, *((*uint16)(p))) + case tI32: + b = appendUint32(b, *((*uint32)(p))) + case tENUM: + b = appendUint32(b, uint32(*((*int64)(p)))) + case tI64, tDOUBLE: + b = appendUint64(b, *((*uint64)(p))) + case tSTRING: + s := *((*string)(p)) + b = appendUint32(b, uint32(len(s))) + b = append(b, s...) + } + return b, nil + } else { + return t.AppendFunc(t, b, p) + } +} diff --git a/internal/reflect/append_gen.sh b/internal/reflect/append_gen.sh new file mode 100755 index 0000000..81b6880 --- /dev/null +++ b/internal/reflect/append_gen.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# +# Copyright 2024 CloudWeGo Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FRUGAL_GEN_APPEND_MAP_FILE="append_map_gen.go" +FRUGAL_GEN_APPEND_LIST_FILE="append_list_gen.go" + +rm -f $FRUGAL_GEN_APPEND_MAP_FILE +rm -f $FRUGAL_GEN_APPEND_LIST_FILE + +exec go test -v -run=TestGenAppend -gencode=true diff --git a/internal/reflect/append_gen_test.go b/internal/reflect/append_gen_test.go new file mode 100644 index 0000000..2b5c459 --- /dev/null +++ b/internal/reflect/append_gen_test.go @@ -0,0 +1,99 @@ +/* + * Copyright 2024 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package reflect + +import ( + "bytes" + "flag" + "fmt" + "strings" +) + +var ( + gencode = flag.Bool("gencode", false, "generate list/map code for better performance") +) + +var tOTHER = ttype(0xee) // must not in use, only for generating code + +func init() { + t2s[tOTHER] = "Other" // makes ttype2str work +} + +func ttype2FuncType(t ttype) string { + switch t { + case tSTRUCT, tMAP, tSET, tLIST: + t = tOTHER + case tDOUBLE: + t = tI64 + } + return ttype2str(t) +} + +var ( + defineErr = map[ttype]bool{tOTHER: true} + defineStr = map[ttype]bool{tSTRING: true} +) + +func getAppendCode(typ ttype, t, p string) string { + t2c := map[ttype]string{ + tBYTE: "b = append(b, *((*byte)({p})))", + tI16: "b = appendUint16(b, *((*uint16)({p})))", + tI32: "b = appendUint32(b, *((*uint32)({p})))", + tI64: "b = appendUint64(b, *((*uint64)({p})))", + tDOUBLE: "b = appendUint64(b, *((*uint64)({p})))", + tENUM: "b = appendUint32(b, uint32(*((*int64)({p}))))", + tSTRING: "s = *((*string)({p})); b = appendUint32(b, uint32(len(s))); b = append(b, s...)", + + // tSTRUCT, tMAP, tSET, tLIST -> tOTHER + tOTHER: `if {t}.IsPointer { + b, err = {t}.AppendFunc({t}, b, *(*unsafe.Pointer)({p})) + } else { + b, err = {t}.AppendFunc({t}, b, {p}) + } + if err != nil { + return b, err +}`, + } + s, ok := t2c[typ] + if !ok { + panic("type doesn't have code: " + ttype2str(typ)) + } + s = strings.ReplaceAll(s, "{t}", t) + s = strings.ReplaceAll(s, "{p}", p) + return s +} + +func codeWithLine(b []byte) string { + p := &strings.Builder{} + p.Grow(len(b) + 5*bytes.Count(b, []byte("\n"))) + + n := 1 + i := 0 + fmt.Fprintf(p, "%4d ", n) + for j := 0; j < len(b); j++ { + if b[j] == '\n' { + p.Write(b[i : j+1]) + i = j + 1 + n++ + fmt.Fprintf(p, "%4d ", n) + } + } + if i < len(b) { + p.Write(b[i:]) + } + return p.String() +} diff --git a/internal/reflect/append_list.go b/internal/reflect/append_list.go new file mode 100644 index 0000000..774e91b --- /dev/null +++ b/internal/reflect/append_list.go @@ -0,0 +1,67 @@ +/* + * Copyright 2024 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package reflect + +import "unsafe" + +var listAppendFuncs = map[ttype]appendFuncType{} + +func updateListAppendFunc(t *tType) { + if t.T != tLIST && t.T != tSET { + panic("[bug] type mismatch, got: " + ttype2str(t.T)) + } + f, ok := listAppendFuncs[t.V.T] + if ok { + t.AppendFunc = f + return + } + t.AppendFunc = appendListAny +} + +func registerListAppendFunc(t ttype, f appendFuncType) { + listAppendFuncs[t] = f +} + +func appendListHeader(t *tType, b []byte, p unsafe.Pointer) ([]byte, uint32, unsafe.Pointer) { + if *(*unsafe.Pointer)(p) == nil { + return append(b, byte(t.WT), 0, 0, 0, 0), 0, nil + } + h := (*sliceHeader)(p) + n := uint32(h.Len) + return append(b, byte(t.WT), + byte(n>>24), byte(n>>16), byte(n>>8), byte(n)), + n, h.UnsafePointer() +} + +func appendListAny(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + t = t.V + b, n, vp := appendListHeader(t, b, p) + if n == 0 { + return b, nil + } + var err error + for i := uint32(0); i < n; i++ { + if i != 0 { + vp = unsafe.Add(vp, t.Size) // move to next element + } + b, err = appendAny(t, b, vp) + if err != nil { + return b, err + } + } + return b, nil +} diff --git a/internal/reflect/append_list_gen.go b/internal/reflect/append_list_gen.go new file mode 100644 index 0000000..bfa1e40 --- /dev/null +++ b/internal/reflect/append_list_gen.go @@ -0,0 +1,152 @@ +/* + * Copyright 2024 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package reflect + +import "unsafe" + +// This File is generated by append_gen.sh. DO NOT EDIT. +// Template and code can be found in append_list_gen_test.go. + +func init() { + registerListAppendFunc(tBYTE, appendList_I08) + registerListAppendFunc(tI16, appendList_I16) + registerListAppendFunc(tI32, appendList_I32) + registerListAppendFunc(tI64, appendList_I64) + registerListAppendFunc(tDOUBLE, appendList_I64) + registerListAppendFunc(tENUM, appendList_ENUM) + registerListAppendFunc(tSTRING, appendList_STRING) + registerListAppendFunc(tSTRUCT, appendList_Other) + registerListAppendFunc(tMAP, appendList_Other) + registerListAppendFunc(tSET, appendList_Other) + registerListAppendFunc(tLIST, appendList_Other) +} + +func appendList_I08(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + t = t.V + b, n, vp := appendListHeader(t, b, p) + if n == 0 { + return b, nil + } + for i := uint32(0); i < n; i++ { + if i != 0 { + vp = unsafe.Add(vp, t.Size) + } + b = append(b, *((*byte)(vp))) + } + return b, nil +} + +func appendList_I16(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + t = t.V + b, n, vp := appendListHeader(t, b, p) + if n == 0 { + return b, nil + } + for i := uint32(0); i < n; i++ { + if i != 0 { + vp = unsafe.Add(vp, t.Size) + } + b = appendUint16(b, *((*uint16)(vp))) + } + return b, nil +} + +func appendList_I32(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + t = t.V + b, n, vp := appendListHeader(t, b, p) + if n == 0 { + return b, nil + } + for i := uint32(0); i < n; i++ { + if i != 0 { + vp = unsafe.Add(vp, t.Size) + } + b = appendUint32(b, *((*uint32)(vp))) + } + return b, nil +} + +func appendList_I64(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + t = t.V + b, n, vp := appendListHeader(t, b, p) + if n == 0 { + return b, nil + } + for i := uint32(0); i < n; i++ { + if i != 0 { + vp = unsafe.Add(vp, t.Size) + } + b = appendUint64(b, *((*uint64)(vp))) + } + return b, nil +} + +func appendList_ENUM(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + t = t.V + b, n, vp := appendListHeader(t, b, p) + if n == 0 { + return b, nil + } + for i := uint32(0); i < n; i++ { + if i != 0 { + vp = unsafe.Add(vp, t.Size) + } + b = appendUint32(b, uint32(*((*int64)(vp)))) + } + return b, nil +} + +func appendList_STRING(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + t = t.V + b, n, vp := appendListHeader(t, b, p) + if n == 0 { + return b, nil + } + var s string + for i := uint32(0); i < n; i++ { + if i != 0 { + vp = unsafe.Add(vp, t.Size) + } + s = *((*string)(vp)) + b = appendUint32(b, uint32(len(s))) + b = append(b, s...) + } + return b, nil +} + +func appendList_Other(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + t = t.V + b, n, vp := appendListHeader(t, b, p) + if n == 0 { + return b, nil + } + var err error + for i := uint32(0); i < n; i++ { + if i != 0 { + vp = unsafe.Add(vp, t.Size) + } + if t.IsPointer { + b, err = t.AppendFunc(t, b, *(*unsafe.Pointer)(vp)) + } else { + b, err = t.AppendFunc(t, b, vp) + } + if err != nil { + return b, err + } + } + return b, nil +} diff --git a/internal/reflect/append_list_gen_test.go b/internal/reflect/append_list_gen_test.go new file mode 100644 index 0000000..92edeeb --- /dev/null +++ b/internal/reflect/append_list_gen_test.go @@ -0,0 +1,181 @@ +/* + * Copyright 2024 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package reflect + +import ( + "bytes" + "fmt" + "go/format" + "os" + "testing" + + "github.com/stretchr/testify/require" +) + +const appendListFileName = "append_list_gen.go" + +func TestGenAppendListCode(t *testing.T) { + if *gencode { + genAppendListCode(t, appendListFileName) + return + } + + type EnumType int64 + + type Msg struct { + X int64 `frugal:"1,default,i64"` + Y int64 `frugal:"2,default,i64"` + } + + type TestStruct struct { + L1 []int8 `frugal:"1,optional,list"` + L2 []int16 `frugal:"2,optional,list"` + L3 []int32 `frugal:"3,optional,list"` + L4 []int64 `frugal:"4,optional,list"` + L5 []EnumType `frugal:"5,optional,list"` + L6 []string `frugal:"6,optional,list"` + L7 []*Msg `frugal:"7,optional,list"` + } + + var p0, p1 *TestStruct + var b []byte + var err error + + p0 = &TestStruct{ + L1: []int8{11, 12}, + L2: []int16{21, 22}, + L3: []int32{31, 32}, + L4: []int64{41, 42}, + L5: []EnumType{51, 52}, + L6: []string{"61", "62"}, + L7: []*Msg{{X: 71, Y: 72}, {X: 73, Y: 74}}, + } + b, err = Append(nil, p0) + require.NoError(t, err) + + p1 = &TestStruct{} + _, err = Decode(b, p1) + require.NoError(t, err) + require.Equal(t, p0, p1) + + // Empty list + p0 = &TestStruct{ + L1: []int8{}, + L2: []int16{}, + L3: []int32{}, + L4: []int64{}, + L5: []EnumType{}, + L6: []string{}, + L7: []*Msg{}, + } + b, err = Append(nil, p0) + require.NoError(t, err) + + p1 = &TestStruct{} + _, err = Decode(b, p1) + require.NoError(t, err) + require.Equal(t, p0, p1) + +} + +func genAppendListCode(t *testing.T, filename string) { + + defineErr := map[ttype]bool{tOTHER: true} + defineStr := map[ttype]bool{tSTRING: true} + + f := &bytes.Buffer{} + f.WriteString(appendListGenFileHeader) + + // func init + fmt.Fprintln(f, "func init() {") + supportTypes := []ttype{ + tBYTE, tI16, tI32, tI64, tDOUBLE, + tENUM, tSTRING, tSTRUCT, tMAP, tSET, tLIST, + } + t2var := map[ttype]string{ + tBYTE: "tBYTE", tI16: "tI16", tI32: "tI32", tI64: "tI64", tDOUBLE: "tDOUBLE", + tENUM: "tENUM", tSTRING: "tSTRING", + tSTRUCT: "tSTRUCT", tMAP: "tMAP", tSET: "tSET", tLIST: "tLIST", + } + for _, v := range supportTypes { + fmt.Fprintf(f, "registerListAppendFunc(%s, %s)\n", + t2var[v], appendListFuncName(v)) + } + fmt.Fprintln(f, "}") + fmt.Fprintln(f, "") + + // func appendList_XXX + for _, v := range []ttype{tBYTE, tI16, tI32, tI64, tENUM, tSTRING, tOTHER} { + fmt.Fprintf(f, "func %s(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) {\n", + appendListFuncName(v)) + fmt.Fprintln(f, "t = t.V") + fmt.Fprintln(f, "b, n, vp := appendListHeader(t, b, p)") + fmt.Fprintln(f, "if n == 0 { return b, nil }") + if defineErr[v] { + fmt.Fprintln(f, "var err error") + } else if defineStr[v] { + fmt.Fprintln(f, "var s string") + } + fmt.Fprintln(f, "for i := uint32(0); i < n; i++ {") + fmt.Fprintln(f, "if i != 0 { vp = unsafe.Add(vp, t.Size) }") + fmt.Fprintln(f, getAppendCode(v, "t", "vp")) + fmt.Fprintln(f, "}") + fmt.Fprintln(f, "return b, nil") + fmt.Fprintln(f, "}") + fmt.Fprintln(f, "") + } + + fileb, err := format.Source(f.Bytes()) + if err != nil { + t.Log(codeWithLine(f.Bytes())) + t.Fatal(err) + } + err = os.WriteFile(filename, fileb, 0o644) + if err != nil { + t.Fatal(err) + } + t.Logf("generated: %s", filename) +} + +func appendListFuncName(t ttype) string { + return fmt.Sprintf("appendList_%s", ttype2FuncType(t)) +} + +const appendListGenFileHeader = `/* + * Copyright 2024 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package reflect + +import "unsafe" + +// This File is generated by append_gen.sh. DO NOT EDIT. +// Template and code can be found in append_list_gen_test.go. + +` diff --git a/internal/reflect/append_list_test.go b/internal/reflect/append_list_test.go new file mode 100644 index 0000000..4c720aa --- /dev/null +++ b/internal/reflect/append_list_test.go @@ -0,0 +1,46 @@ +/* + * Copyright 2024 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package reflect + +import ( + "testing" + "unsafe" + + "github.com/cloudwego/gopkg/protocol/thrift" + "github.com/stretchr/testify/require" +) + +func TestAppendListAny(t *testing.T) { + typ := &tType{T: tLIST} + typ.V = &tType{T: tI64, WT: tI64, Size: 8, SimpleType: true} + v := []int64{1, 2} + b, err := appendListAny(typ, nil, unsafe.Pointer(&v)) + require.NoError(t, err) + + x := thrift.BinaryProtocol{} + expectb := x.AppendListBegin(nil, thrift.I64, 2) + expectb = x.AppendI64(expectb, 1) + expectb = x.AppendI64(expectb, 2) + require.Equal(t, expectb, b) + + // empty case + v = nil + b, err = appendListAny(typ, nil, unsafe.Pointer(&v)) + require.NoError(t, err) + expectb = x.AppendListBegin(nil, thrift.I64, 0) + require.Equal(t, expectb, b) +} diff --git a/internal/reflect/append_map.go b/internal/reflect/append_map.go new file mode 100644 index 0000000..1895a62 --- /dev/null +++ b/internal/reflect/append_map.go @@ -0,0 +1,81 @@ +/* + * Copyright 2024 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package reflect + +import ( + "errors" + "unsafe" +) + +var mapAppendFuncs = map[struct{ k, v ttype }]appendFuncType{} + +func updateMapAppendFunc(t *tType) { + if t.T != tMAP { + panic("[bug] type mismatch, got: " + ttype2str(t.T)) + } + + f, ok := mapAppendFuncs[struct{ k, v ttype }{k: t.K.T, v: t.V.T}] + if ok { + t.AppendFunc = f + return + } + t.AppendFunc = appendMapAnyAny +} + +func registerMapAppendFunc(k, v ttype, f appendFuncType) { + mapAppendFuncs[struct{ k, v ttype }{k: k, v: v}] = f +} + +func checkMapN(n uint32) error { + if n == 0 { + return nil + } + return errors.New("map size changed during encoding") +} + +func appendMapHeader(t *tType, b []byte, p unsafe.Pointer) ([]byte, uint32) { + var n uint32 + if *(*unsafe.Pointer)(p) != nil { + n = uint32(maplen(*(*unsafe.Pointer)(p))) + } + return append(b, byte(t.K.WT), byte(t.V.WT), + byte(n>>24), byte(n>>16), byte(n>>8), byte(n)), n +} + +// this func will be replaced by funcs defined in append_map_gen.go +// see init() in append_map_gen.go for details. +func appendMapAnyAny(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + + var err error + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + b, err = appendAny(t.K, b, kp) + if err != nil { + return b, err + } + b, err = appendAny(t.V, b, vp) + if err != nil { + return b, err + } + } + return b, checkMapN(n) +} diff --git a/internal/reflect/append_map_gen.go b/internal/reflect/append_map_gen.go new file mode 100644 index 0000000..b6e533d --- /dev/null +++ b/internal/reflect/append_map_gen.go @@ -0,0 +1,984 @@ +/* + * Copyright 2024 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package reflect + +import "unsafe" + +// This File is generated by append_gen.sh. DO NOT EDIT. +// Template and code can be found in append_map_gen_test.go. + +func init() { + registerMapAppendFunc(tBYTE, tBYTE, appendMap_I08_I08) + registerMapAppendFunc(tBYTE, tI16, appendMap_I08_I16) + registerMapAppendFunc(tBYTE, tI32, appendMap_I08_I32) + registerMapAppendFunc(tBYTE, tI64, appendMap_I08_I64) + registerMapAppendFunc(tBYTE, tDOUBLE, appendMap_I08_I64) + registerMapAppendFunc(tBYTE, tENUM, appendMap_I08_ENUM) + registerMapAppendFunc(tBYTE, tSTRING, appendMap_I08_STRING) + registerMapAppendFunc(tBYTE, tSTRUCT, appendMap_I08_Other) + registerMapAppendFunc(tBYTE, tMAP, appendMap_I08_Other) + registerMapAppendFunc(tBYTE, tSET, appendMap_I08_Other) + registerMapAppendFunc(tBYTE, tLIST, appendMap_I08_Other) + registerMapAppendFunc(tI16, tBYTE, appendMap_I16_I08) + registerMapAppendFunc(tI16, tI16, appendMap_I16_I16) + registerMapAppendFunc(tI16, tI32, appendMap_I16_I32) + registerMapAppendFunc(tI16, tI64, appendMap_I16_I64) + registerMapAppendFunc(tI16, tDOUBLE, appendMap_I16_I64) + registerMapAppendFunc(tI16, tENUM, appendMap_I16_ENUM) + registerMapAppendFunc(tI16, tSTRING, appendMap_I16_STRING) + registerMapAppendFunc(tI16, tSTRUCT, appendMap_I16_Other) + registerMapAppendFunc(tI16, tMAP, appendMap_I16_Other) + registerMapAppendFunc(tI16, tSET, appendMap_I16_Other) + registerMapAppendFunc(tI16, tLIST, appendMap_I16_Other) + registerMapAppendFunc(tI32, tBYTE, appendMap_I32_I08) + registerMapAppendFunc(tI32, tI16, appendMap_I32_I16) + registerMapAppendFunc(tI32, tI32, appendMap_I32_I32) + registerMapAppendFunc(tI32, tI64, appendMap_I32_I64) + registerMapAppendFunc(tI32, tDOUBLE, appendMap_I32_I64) + registerMapAppendFunc(tI32, tENUM, appendMap_I32_ENUM) + registerMapAppendFunc(tI32, tSTRING, appendMap_I32_STRING) + registerMapAppendFunc(tI32, tSTRUCT, appendMap_I32_Other) + registerMapAppendFunc(tI32, tMAP, appendMap_I32_Other) + registerMapAppendFunc(tI32, tSET, appendMap_I32_Other) + registerMapAppendFunc(tI32, tLIST, appendMap_I32_Other) + registerMapAppendFunc(tI64, tBYTE, appendMap_I64_I08) + registerMapAppendFunc(tI64, tI16, appendMap_I64_I16) + registerMapAppendFunc(tI64, tI32, appendMap_I64_I32) + registerMapAppendFunc(tI64, tI64, appendMap_I64_I64) + registerMapAppendFunc(tI64, tDOUBLE, appendMap_I64_I64) + registerMapAppendFunc(tI64, tENUM, appendMap_I64_ENUM) + registerMapAppendFunc(tI64, tSTRING, appendMap_I64_STRING) + registerMapAppendFunc(tI64, tSTRUCT, appendMap_I64_Other) + registerMapAppendFunc(tI64, tMAP, appendMap_I64_Other) + registerMapAppendFunc(tI64, tSET, appendMap_I64_Other) + registerMapAppendFunc(tI64, tLIST, appendMap_I64_Other) + registerMapAppendFunc(tDOUBLE, tBYTE, appendMap_I64_I08) + registerMapAppendFunc(tDOUBLE, tI16, appendMap_I64_I16) + registerMapAppendFunc(tDOUBLE, tI32, appendMap_I64_I32) + registerMapAppendFunc(tDOUBLE, tI64, appendMap_I64_I64) + registerMapAppendFunc(tDOUBLE, tDOUBLE, appendMap_I64_I64) + registerMapAppendFunc(tDOUBLE, tENUM, appendMap_I64_ENUM) + registerMapAppendFunc(tDOUBLE, tSTRING, appendMap_I64_STRING) + registerMapAppendFunc(tDOUBLE, tSTRUCT, appendMap_I64_Other) + registerMapAppendFunc(tDOUBLE, tMAP, appendMap_I64_Other) + registerMapAppendFunc(tDOUBLE, tSET, appendMap_I64_Other) + registerMapAppendFunc(tDOUBLE, tLIST, appendMap_I64_Other) + registerMapAppendFunc(tENUM, tBYTE, appendMap_ENUM_I08) + registerMapAppendFunc(tENUM, tI16, appendMap_ENUM_I16) + registerMapAppendFunc(tENUM, tI32, appendMap_ENUM_I32) + registerMapAppendFunc(tENUM, tI64, appendMap_ENUM_I64) + registerMapAppendFunc(tENUM, tDOUBLE, appendMap_ENUM_I64) + registerMapAppendFunc(tENUM, tENUM, appendMap_ENUM_ENUM) + registerMapAppendFunc(tENUM, tSTRING, appendMap_ENUM_STRING) + registerMapAppendFunc(tENUM, tSTRUCT, appendMap_ENUM_Other) + registerMapAppendFunc(tENUM, tMAP, appendMap_ENUM_Other) + registerMapAppendFunc(tENUM, tSET, appendMap_ENUM_Other) + registerMapAppendFunc(tENUM, tLIST, appendMap_ENUM_Other) + registerMapAppendFunc(tSTRING, tBYTE, appendMap_STRING_I08) + registerMapAppendFunc(tSTRING, tI16, appendMap_STRING_I16) + registerMapAppendFunc(tSTRING, tI32, appendMap_STRING_I32) + registerMapAppendFunc(tSTRING, tI64, appendMap_STRING_I64) + registerMapAppendFunc(tSTRING, tDOUBLE, appendMap_STRING_I64) + registerMapAppendFunc(tSTRING, tENUM, appendMap_STRING_ENUM) + registerMapAppendFunc(tSTRING, tSTRING, appendMap_STRING_STRING) + registerMapAppendFunc(tSTRING, tSTRUCT, appendMap_STRING_Other) + registerMapAppendFunc(tSTRING, tMAP, appendMap_STRING_Other) + registerMapAppendFunc(tSTRING, tSET, appendMap_STRING_Other) + registerMapAppendFunc(tSTRING, tLIST, appendMap_STRING_Other) + registerMapAppendFunc(tSTRUCT, tBYTE, appendMap_Other_I08) + registerMapAppendFunc(tSTRUCT, tI16, appendMap_Other_I16) + registerMapAppendFunc(tSTRUCT, tI32, appendMap_Other_I32) + registerMapAppendFunc(tSTRUCT, tI64, appendMap_Other_I64) + registerMapAppendFunc(tSTRUCT, tDOUBLE, appendMap_Other_I64) + registerMapAppendFunc(tSTRUCT, tENUM, appendMap_Other_ENUM) + registerMapAppendFunc(tSTRUCT, tSTRING, appendMap_Other_STRING) + registerMapAppendFunc(tSTRUCT, tSTRUCT, appendMap_Other_Other) + registerMapAppendFunc(tSTRUCT, tMAP, appendMap_Other_Other) + registerMapAppendFunc(tSTRUCT, tSET, appendMap_Other_Other) + registerMapAppendFunc(tSTRUCT, tLIST, appendMap_Other_Other) + registerMapAppendFunc(tMAP, tBYTE, appendMap_Other_I08) + registerMapAppendFunc(tMAP, tI16, appendMap_Other_I16) + registerMapAppendFunc(tMAP, tI32, appendMap_Other_I32) + registerMapAppendFunc(tMAP, tI64, appendMap_Other_I64) + registerMapAppendFunc(tMAP, tDOUBLE, appendMap_Other_I64) + registerMapAppendFunc(tMAP, tENUM, appendMap_Other_ENUM) + registerMapAppendFunc(tMAP, tSTRING, appendMap_Other_STRING) + registerMapAppendFunc(tMAP, tSTRUCT, appendMap_Other_Other) + registerMapAppendFunc(tMAP, tMAP, appendMap_Other_Other) + registerMapAppendFunc(tMAP, tSET, appendMap_Other_Other) + registerMapAppendFunc(tMAP, tLIST, appendMap_Other_Other) + registerMapAppendFunc(tSET, tBYTE, appendMap_Other_I08) + registerMapAppendFunc(tSET, tI16, appendMap_Other_I16) + registerMapAppendFunc(tSET, tI32, appendMap_Other_I32) + registerMapAppendFunc(tSET, tI64, appendMap_Other_I64) + registerMapAppendFunc(tSET, tDOUBLE, appendMap_Other_I64) + registerMapAppendFunc(tSET, tENUM, appendMap_Other_ENUM) + registerMapAppendFunc(tSET, tSTRING, appendMap_Other_STRING) + registerMapAppendFunc(tSET, tSTRUCT, appendMap_Other_Other) + registerMapAppendFunc(tSET, tMAP, appendMap_Other_Other) + registerMapAppendFunc(tSET, tSET, appendMap_Other_Other) + registerMapAppendFunc(tSET, tLIST, appendMap_Other_Other) + registerMapAppendFunc(tLIST, tBYTE, appendMap_Other_I08) + registerMapAppendFunc(tLIST, tI16, appendMap_Other_I16) + registerMapAppendFunc(tLIST, tI32, appendMap_Other_I32) + registerMapAppendFunc(tLIST, tI64, appendMap_Other_I64) + registerMapAppendFunc(tLIST, tDOUBLE, appendMap_Other_I64) + registerMapAppendFunc(tLIST, tENUM, appendMap_Other_ENUM) + registerMapAppendFunc(tLIST, tSTRING, appendMap_Other_STRING) + registerMapAppendFunc(tLIST, tSTRUCT, appendMap_Other_Other) + registerMapAppendFunc(tLIST, tMAP, appendMap_Other_Other) + registerMapAppendFunc(tLIST, tSET, appendMap_Other_Other) + registerMapAppendFunc(tLIST, tLIST, appendMap_Other_Other) +} + +func appendMap_I08_I08(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + b = append(b, *((*byte)(kp))) + b = append(b, *((*byte)(vp))) + } + return b, checkMapN(n) +} + +func appendMap_I08_I16(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + b = append(b, *((*byte)(kp))) + b = appendUint16(b, *((*uint16)(vp))) + } + return b, checkMapN(n) +} + +func appendMap_I08_I32(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + b = append(b, *((*byte)(kp))) + b = appendUint32(b, *((*uint32)(vp))) + } + return b, checkMapN(n) +} + +func appendMap_I08_I64(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + b = append(b, *((*byte)(kp))) + b = appendUint64(b, *((*uint64)(vp))) + } + return b, checkMapN(n) +} + +func appendMap_I08_ENUM(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + b = append(b, *((*byte)(kp))) + b = appendUint32(b, uint32(*((*int64)(vp)))) + } + return b, checkMapN(n) +} + +func appendMap_I08_STRING(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + var s string + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + b = append(b, *((*byte)(kp))) + s = *((*string)(vp)) + b = appendUint32(b, uint32(len(s))) + b = append(b, s...) + } + return b, checkMapN(n) +} + +func appendMap_I08_Other(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + var err error + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + b = append(b, *((*byte)(kp))) + if t.V.IsPointer { + b, err = t.V.AppendFunc(t.V, b, *(*unsafe.Pointer)(vp)) + } else { + b, err = t.V.AppendFunc(t.V, b, vp) + } + if err != nil { + return b, err + } + } + return b, checkMapN(n) +} + +func appendMap_I16_I08(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + b = appendUint16(b, *((*uint16)(kp))) + b = append(b, *((*byte)(vp))) + } + return b, checkMapN(n) +} + +func appendMap_I16_I16(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + b = appendUint16(b, *((*uint16)(kp))) + b = appendUint16(b, *((*uint16)(vp))) + } + return b, checkMapN(n) +} + +func appendMap_I16_I32(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + b = appendUint16(b, *((*uint16)(kp))) + b = appendUint32(b, *((*uint32)(vp))) + } + return b, checkMapN(n) +} + +func appendMap_I16_I64(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + b = appendUint16(b, *((*uint16)(kp))) + b = appendUint64(b, *((*uint64)(vp))) + } + return b, checkMapN(n) +} + +func appendMap_I16_ENUM(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + b = appendUint16(b, *((*uint16)(kp))) + b = appendUint32(b, uint32(*((*int64)(vp)))) + } + return b, checkMapN(n) +} + +func appendMap_I16_STRING(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + var s string + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + b = appendUint16(b, *((*uint16)(kp))) + s = *((*string)(vp)) + b = appendUint32(b, uint32(len(s))) + b = append(b, s...) + } + return b, checkMapN(n) +} + +func appendMap_I16_Other(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + var err error + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + b = appendUint16(b, *((*uint16)(kp))) + if t.V.IsPointer { + b, err = t.V.AppendFunc(t.V, b, *(*unsafe.Pointer)(vp)) + } else { + b, err = t.V.AppendFunc(t.V, b, vp) + } + if err != nil { + return b, err + } + } + return b, checkMapN(n) +} + +func appendMap_I32_I08(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + b = appendUint32(b, *((*uint32)(kp))) + b = append(b, *((*byte)(vp))) + } + return b, checkMapN(n) +} + +func appendMap_I32_I16(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + b = appendUint32(b, *((*uint32)(kp))) + b = appendUint16(b, *((*uint16)(vp))) + } + return b, checkMapN(n) +} + +func appendMap_I32_I32(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + b = appendUint32(b, *((*uint32)(kp))) + b = appendUint32(b, *((*uint32)(vp))) + } + return b, checkMapN(n) +} + +func appendMap_I32_I64(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + b = appendUint32(b, *((*uint32)(kp))) + b = appendUint64(b, *((*uint64)(vp))) + } + return b, checkMapN(n) +} + +func appendMap_I32_ENUM(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + b = appendUint32(b, *((*uint32)(kp))) + b = appendUint32(b, uint32(*((*int64)(vp)))) + } + return b, checkMapN(n) +} + +func appendMap_I32_STRING(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + var s string + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + b = appendUint32(b, *((*uint32)(kp))) + s = *((*string)(vp)) + b = appendUint32(b, uint32(len(s))) + b = append(b, s...) + } + return b, checkMapN(n) +} + +func appendMap_I32_Other(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + var err error + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + b = appendUint32(b, *((*uint32)(kp))) + if t.V.IsPointer { + b, err = t.V.AppendFunc(t.V, b, *(*unsafe.Pointer)(vp)) + } else { + b, err = t.V.AppendFunc(t.V, b, vp) + } + if err != nil { + return b, err + } + } + return b, checkMapN(n) +} + +func appendMap_I64_I08(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + b = appendUint64(b, *((*uint64)(kp))) + b = append(b, *((*byte)(vp))) + } + return b, checkMapN(n) +} + +func appendMap_I64_I16(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + b = appendUint64(b, *((*uint64)(kp))) + b = appendUint16(b, *((*uint16)(vp))) + } + return b, checkMapN(n) +} + +func appendMap_I64_I32(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + b = appendUint64(b, *((*uint64)(kp))) + b = appendUint32(b, *((*uint32)(vp))) + } + return b, checkMapN(n) +} + +func appendMap_I64_I64(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + b = appendUint64(b, *((*uint64)(kp))) + b = appendUint64(b, *((*uint64)(vp))) + } + return b, checkMapN(n) +} + +func appendMap_I64_ENUM(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + b = appendUint64(b, *((*uint64)(kp))) + b = appendUint32(b, uint32(*((*int64)(vp)))) + } + return b, checkMapN(n) +} + +func appendMap_I64_STRING(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + var s string + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + b = appendUint64(b, *((*uint64)(kp))) + s = *((*string)(vp)) + b = appendUint32(b, uint32(len(s))) + b = append(b, s...) + } + return b, checkMapN(n) +} + +func appendMap_I64_Other(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + var err error + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + b = appendUint64(b, *((*uint64)(kp))) + if t.V.IsPointer { + b, err = t.V.AppendFunc(t.V, b, *(*unsafe.Pointer)(vp)) + } else { + b, err = t.V.AppendFunc(t.V, b, vp) + } + if err != nil { + return b, err + } + } + return b, checkMapN(n) +} + +func appendMap_ENUM_I08(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + b = appendUint32(b, uint32(*((*int64)(kp)))) + b = append(b, *((*byte)(vp))) + } + return b, checkMapN(n) +} + +func appendMap_ENUM_I16(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + b = appendUint32(b, uint32(*((*int64)(kp)))) + b = appendUint16(b, *((*uint16)(vp))) + } + return b, checkMapN(n) +} + +func appendMap_ENUM_I32(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + b = appendUint32(b, uint32(*((*int64)(kp)))) + b = appendUint32(b, *((*uint32)(vp))) + } + return b, checkMapN(n) +} + +func appendMap_ENUM_I64(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + b = appendUint32(b, uint32(*((*int64)(kp)))) + b = appendUint64(b, *((*uint64)(vp))) + } + return b, checkMapN(n) +} + +func appendMap_ENUM_ENUM(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + b = appendUint32(b, uint32(*((*int64)(kp)))) + b = appendUint32(b, uint32(*((*int64)(vp)))) + } + return b, checkMapN(n) +} + +func appendMap_ENUM_STRING(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + var s string + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + b = appendUint32(b, uint32(*((*int64)(kp)))) + s = *((*string)(vp)) + b = appendUint32(b, uint32(len(s))) + b = append(b, s...) + } + return b, checkMapN(n) +} + +func appendMap_ENUM_Other(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + var err error + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + b = appendUint32(b, uint32(*((*int64)(kp)))) + if t.V.IsPointer { + b, err = t.V.AppendFunc(t.V, b, *(*unsafe.Pointer)(vp)) + } else { + b, err = t.V.AppendFunc(t.V, b, vp) + } + if err != nil { + return b, err + } + } + return b, checkMapN(n) +} + +func appendMap_STRING_I08(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + var s string + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + s = *((*string)(kp)) + b = appendUint32(b, uint32(len(s))) + b = append(b, s...) + b = append(b, *((*byte)(vp))) + } + return b, checkMapN(n) +} + +func appendMap_STRING_I16(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + var s string + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + s = *((*string)(kp)) + b = appendUint32(b, uint32(len(s))) + b = append(b, s...) + b = appendUint16(b, *((*uint16)(vp))) + } + return b, checkMapN(n) +} + +func appendMap_STRING_I32(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + var s string + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + s = *((*string)(kp)) + b = appendUint32(b, uint32(len(s))) + b = append(b, s...) + b = appendUint32(b, *((*uint32)(vp))) + } + return b, checkMapN(n) +} + +func appendMap_STRING_I64(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + var s string + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + s = *((*string)(kp)) + b = appendUint32(b, uint32(len(s))) + b = append(b, s...) + b = appendUint64(b, *((*uint64)(vp))) + } + return b, checkMapN(n) +} + +func appendMap_STRING_ENUM(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + var s string + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + s = *((*string)(kp)) + b = appendUint32(b, uint32(len(s))) + b = append(b, s...) + b = appendUint32(b, uint32(*((*int64)(vp)))) + } + return b, checkMapN(n) +} + +func appendMap_STRING_STRING(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + var s string + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + s = *((*string)(kp)) + b = appendUint32(b, uint32(len(s))) + b = append(b, s...) + s = *((*string)(vp)) + b = appendUint32(b, uint32(len(s))) + b = append(b, s...) + } + return b, checkMapN(n) +} + +func appendMap_STRING_Other(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + var err error + var s string + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + s = *((*string)(kp)) + b = appendUint32(b, uint32(len(s))) + b = append(b, s...) + if t.V.IsPointer { + b, err = t.V.AppendFunc(t.V, b, *(*unsafe.Pointer)(vp)) + } else { + b, err = t.V.AppendFunc(t.V, b, vp) + } + if err != nil { + return b, err + } + } + return b, checkMapN(n) +} + +func appendMap_Other_I08(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + var err error + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + if t.K.IsPointer { + b, err = t.K.AppendFunc(t.K, b, *(*unsafe.Pointer)(kp)) + } else { + b, err = t.K.AppendFunc(t.K, b, kp) + } + if err != nil { + return b, err + } + b = append(b, *((*byte)(vp))) + } + return b, checkMapN(n) +} + +func appendMap_Other_I16(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + var err error + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + if t.K.IsPointer { + b, err = t.K.AppendFunc(t.K, b, *(*unsafe.Pointer)(kp)) + } else { + b, err = t.K.AppendFunc(t.K, b, kp) + } + if err != nil { + return b, err + } + b = appendUint16(b, *((*uint16)(vp))) + } + return b, checkMapN(n) +} + +func appendMap_Other_I32(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + var err error + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + if t.K.IsPointer { + b, err = t.K.AppendFunc(t.K, b, *(*unsafe.Pointer)(kp)) + } else { + b, err = t.K.AppendFunc(t.K, b, kp) + } + if err != nil { + return b, err + } + b = appendUint32(b, *((*uint32)(vp))) + } + return b, checkMapN(n) +} + +func appendMap_Other_I64(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + var err error + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + if t.K.IsPointer { + b, err = t.K.AppendFunc(t.K, b, *(*unsafe.Pointer)(kp)) + } else { + b, err = t.K.AppendFunc(t.K, b, kp) + } + if err != nil { + return b, err + } + b = appendUint64(b, *((*uint64)(vp))) + } + return b, checkMapN(n) +} + +func appendMap_Other_ENUM(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + var err error + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + if t.K.IsPointer { + b, err = t.K.AppendFunc(t.K, b, *(*unsafe.Pointer)(kp)) + } else { + b, err = t.K.AppendFunc(t.K, b, kp) + } + if err != nil { + return b, err + } + b = appendUint32(b, uint32(*((*int64)(vp)))) + } + return b, checkMapN(n) +} + +func appendMap_Other_STRING(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + var err error + var s string + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + if t.K.IsPointer { + b, err = t.K.AppendFunc(t.K, b, *(*unsafe.Pointer)(kp)) + } else { + b, err = t.K.AppendFunc(t.K, b, kp) + } + if err != nil { + return b, err + } + s = *((*string)(vp)) + b = appendUint32(b, uint32(len(s))) + b = append(b, s...) + } + return b, checkMapN(n) +} + +func appendMap_Other_Other(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) { + b, n := appendMapHeader(t, b, p) + if n == 0 { + return b, nil + } + var err error + it := newMapIter(rvWithPtr(t.RV, p)) + for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { + n-- + if t.K.IsPointer { + b, err = t.K.AppendFunc(t.K, b, *(*unsafe.Pointer)(kp)) + } else { + b, err = t.K.AppendFunc(t.K, b, kp) + } + if err != nil { + return b, err + } + if t.V.IsPointer { + b, err = t.V.AppendFunc(t.V, b, *(*unsafe.Pointer)(vp)) + } else { + b, err = t.V.AppendFunc(t.V, b, vp) + } + if err != nil { + return b, err + } + } + return b, checkMapN(n) +} diff --git a/internal/reflect/append_map_gen_test.go b/internal/reflect/append_map_gen_test.go new file mode 100644 index 0000000..3a0f14e --- /dev/null +++ b/internal/reflect/append_map_gen_test.go @@ -0,0 +1,294 @@ +/* + * Copyright 2024 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package reflect + +import ( + "bytes" + "fmt" + "go/format" + "os" + "testing" + + "github.com/stretchr/testify/require" +) + +const appendMapFileName = "append_map_gen.go" + +func TestGenAppendMapCode(t *testing.T) { + if *gencode { + genAppendMapCode(t, appendMapFileName) + return + } + + type EnumType int64 + type EmptyStruct struct{} + + doTest := func(t *testing.T, p0, p1 interface{}) { + t.Helper() + b, err := Append(nil, p0) + require.NoError(t, err) + _, err = Decode(b, p1) + require.NoError(t, err) + require.Equal(t, p0, p1) + } + + { + type TestStruct struct { + M1 map[int8]int8 `frugal:"1,optional,map"` + M2 map[int8]int16 `frugal:"2,optional,map"` + M3 map[int8]int32 `frugal:"3,optional,map"` + M4 map[int8]int64 `frugal:"4,optional,map"` + M5 map[int8]EnumType `frugal:"5,optional,map"` + M6 map[int8]string `frugal:"6,optional,map"` + M7 map[int8]*EmptyStruct `frugal:"7,optional,map"` + } + p0 := &TestStruct{ + M1: map[int8]int8{11: 1, 12: 2}, + M2: map[int8]int16{21: 1, 22: 2}, + M3: map[int8]int32{31: 1, 32: 2}, + M4: map[int8]int64{41: 1, 42: 2}, + M5: map[int8]EnumType{51: 1, 52: 2}, + M6: map[int8]string{61: "1", 62: "2"}, + M7: map[int8]*EmptyStruct{71: {}, 72: {}}, + } + p1 := &TestStruct{} + doTest(t, p0, p1) + } + + { + type TestStruct struct { + M1 map[int16]int8 `frugal:"1,optional,map"` + M2 map[int16]int16 `frugal:"2,optional,map"` + M3 map[int16]int32 `frugal:"3,optional,map"` + M4 map[int16]int64 `frugal:"4,optional,map"` + M5 map[int16]EnumType `frugal:"5,optional,map"` + M6 map[int16]string `frugal:"6,optional,map"` + M7 map[int16]*EmptyStruct `frugal:"7,optional,map"` + } + p0 := &TestStruct{ + M1: map[int16]int8{11: 1, 12: 2}, + M2: map[int16]int16{21: 1, 22: 2}, + M3: map[int16]int32{31: 1, 32: 2}, + M4: map[int16]int64{41: 1, 42: 2}, + M5: map[int16]EnumType{51: 1, 52: 2}, + M6: map[int16]string{61: "1", 62: "2"}, + M7: map[int16]*EmptyStruct{71: {}, 72: {}}, + } + p1 := &TestStruct{} + doTest(t, p0, p1) + } + { + type TestStruct struct { + M1 map[int32]int8 `frugal:"1,optional,map"` + M2 map[int32]int16 `frugal:"2,optional,map"` + M3 map[int32]int32 `frugal:"3,optional,map"` + M4 map[int32]int64 `frugal:"4,optional,map"` + M5 map[int32]EnumType `frugal:"5,optional,map"` + M6 map[int32]string `frugal:"6,optional,map"` + M7 map[int32]*EmptyStruct `frugal:"7,optional,map"` + } + p0 := &TestStruct{ + M1: map[int32]int8{11: 1, 12: 2}, + M2: map[int32]int16{21: 1, 22: 2}, + M3: map[int32]int32{31: 1, 32: 2}, + M4: map[int32]int64{41: 1, 42: 2}, + M5: map[int32]EnumType{51: 1, 52: 2}, + M6: map[int32]string{61: "1", 62: "2"}, + M7: map[int32]*EmptyStruct{71: {}, 72: {}}, + } + p1 := &TestStruct{} + doTest(t, p0, p1) + } + { + type TestStruct struct { + M1 map[int64]int8 `frugal:"1,optional,map"` + M2 map[int64]int16 `frugal:"2,optional,map"` + M3 map[int64]int32 `frugal:"3,optional,map"` + M4 map[int64]int64 `frugal:"4,optional,map"` + M5 map[int64]EnumType `frugal:"5,optional,map"` + M6 map[int64]string `frugal:"6,optional,map"` + M7 map[int64]*EmptyStruct `frugal:"7,optional,map"` + } + p0 := &TestStruct{ + M1: map[int64]int8{11: 1, 12: 2}, + M2: map[int64]int16{21: 1, 22: 2}, + M3: map[int64]int32{31: 1, 32: 2}, + M4: map[int64]int64{41: 1, 42: 2}, + M5: map[int64]EnumType{51: 1, 52: 2}, + M6: map[int64]string{61: "1", 62: "2"}, + M7: map[int64]*EmptyStruct{71: {}, 72: {}}, + } + p1 := &TestStruct{} + doTest(t, p0, p1) + } + { + type TestStruct struct { + M1 map[EnumType]int8 `frugal:"1,optional,map"` + M2 map[EnumType]int16 `frugal:"2,optional,map"` + M3 map[EnumType]int32 `frugal:"3,optional,map"` + M4 map[EnumType]int64 `frugal:"4,optional,map"` + M5 map[EnumType]EnumType `frugal:"5,optional,map"` + M6 map[EnumType]string `frugal:"6,optional,map"` + M7 map[EnumType]*EmptyStruct `frugal:"7,optional,map"` + } + p0 := &TestStruct{ + M1: map[EnumType]int8{11: 1, 12: 2}, + M2: map[EnumType]int16{21: 1, 22: 2}, + M3: map[EnumType]int32{31: 1, 32: 2}, + M4: map[EnumType]int64{41: 1, 42: 2}, + M5: map[EnumType]EnumType{51: 1, 52: 2}, + M6: map[EnumType]string{61: "1", 62: "2"}, + M7: map[EnumType]*EmptyStruct{71: {}, 72: {}}, + } + p1 := &TestStruct{} + doTest(t, p0, p1) + } + { + type TestStruct struct { + M1 map[string]int8 `frugal:"1,optional,map"` + M2 map[string]int16 `frugal:"2,optional,map"` + M3 map[string]int32 `frugal:"3,optional,map"` + M4 map[string]int64 `frugal:"4,optional,map"` + M5 map[string]EnumType `frugal:"5,optional,map"` + M6 map[string]string `frugal:"6,optional,map"` + M7 map[string]*EmptyStruct `frugal:"7,optional,map"` + } + p0 := &TestStruct{ + M1: map[string]int8{"11": 1, "12": 2}, + M2: map[string]int16{"21": 1, "22": 2}, + M3: map[string]int32{"31": 1, "32": 2}, + M4: map[string]int64{"41": 1, "42": 2}, + M5: map[string]EnumType{"51": 1, "52": 2}, + M6: map[string]string{"61": "1", "62": "2"}, + M7: map[string]*EmptyStruct{"71": {}, "72": {}}, + } + p1 := &TestStruct{} + doTest(t, p0, p1) + } + { + type TestStruct struct { + M1 map[*EmptyStruct]int8 `frugal:"1,optional,map"` + M2 map[*EmptyStruct]int16 `frugal:"2,optional,map"` + M3 map[*EmptyStruct]int32 `frugal:"3,optional,map"` + M4 map[*EmptyStruct]int64 `frugal:"4,optional,map"` + M5 map[*EmptyStruct]EnumType `frugal:"5,optional,map"` + M6 map[*EmptyStruct]string `frugal:"6,optional,map"` + M7 map[*EmptyStruct]*EmptyStruct `frugal:"7,optional,map"` + } + p0 := &TestStruct{ + M1: map[*EmptyStruct]int8{{}: 1}, + M2: map[*EmptyStruct]int16{{}: 1}, + M3: map[*EmptyStruct]int32{{}: 1}, + M4: map[*EmptyStruct]int64{{}: 1}, + M5: map[*EmptyStruct]EnumType{{}: 1}, + M6: map[*EmptyStruct]string{{}: "1"}, + M7: map[*EmptyStruct]*EmptyStruct{{}: {}}, + } + p1 := &TestStruct{} + doTest(t, p0, p1) + } +} + +func genAppendMapCode(t *testing.T, filename string) { + f := &bytes.Buffer{} + f.WriteString(appendMapGenFileHeader) + + // func init + fmt.Fprintln(f, "func init() {") + supportTypes := []ttype{ + tBYTE, tI16, tI32, tI64, tDOUBLE, + tENUM, tSTRING, tSTRUCT, tMAP, tSET, tLIST, + } + t2var := map[ttype]string{ + tBYTE: "tBYTE", tI16: "tI16", tI32: "tI32", tI64: "tI64", tDOUBLE: "tDOUBLE", + tENUM: "tENUM", tSTRING: "tSTRING", + tSTRUCT: "tSTRUCT", tMAP: "tMAP", tSET: "tSET", tLIST: "tLIST", + } + for _, k := range supportTypes { + for _, v := range supportTypes { + fmt.Fprintf(f, "registerMapAppendFunc(%s, %s, %s)\n", + t2var[k], t2var[v], appendMapFuncName(k, v)) + } + } + fmt.Fprintln(f, "}") + fmt.Fprintln(f, "") + + // func appendMapXXX + for _, k := range []ttype{tBYTE, tI16, tI32, tI64, tENUM, tSTRING, tOTHER} { + for _, v := range []ttype{tBYTE, tI16, tI32, tI64, tENUM, tSTRING, tOTHER} { + fmt.Fprintf(f, "func %s(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) {\n", + appendMapFuncName(k, v)) + fmt.Fprintln(f, "b, n := appendMapHeader(t, b, p)") + fmt.Fprintln(f, "if n == 0 { return b, nil }") + if defineErr[k] || defineErr[v] { + fmt.Fprintln(f, "var err error") + } + if defineStr[k] || defineStr[v] { + fmt.Fprintln(f, "var s string") + } + fmt.Fprintln(f, "it := newMapIter(rvWithPtr(t.RV, p))") + fmt.Fprintln(f, "for kp, vp := it.Next(); kp != nil;kp, vp = it.Next() {") + fmt.Fprintln(f, "n--") + fmt.Fprintln(f, getAppendCode(k, "t.K", "kp")) + fmt.Fprintln(f, getAppendCode(v, "t.V", "vp")) + fmt.Fprintln(f, "}") + fmt.Fprintln(f, "return b, checkMapN(n)") + fmt.Fprintln(f, "}") + fmt.Fprintln(f, "") + } + } + + fileb, err := format.Source(f.Bytes()) + if err != nil { + t.Log(codeWithLine(f.Bytes())) + t.Fatal(err) + } + err = os.WriteFile(filename, fileb, 0o644) + if err != nil { + t.Fatal(err) + } + t.Logf("generated: %s", filename) +} + +func appendMapFuncName(k, v ttype) string { + return fmt.Sprintf("appendMap_%s_%s", ttype2FuncType(k), ttype2FuncType(v)) +} + +const appendMapGenFileHeader = `/* + * Copyright 2024 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package reflect + +import "unsafe" + +// This File is generated by append_gen.sh. DO NOT EDIT. +// Template and code can be found in append_map_gen_test.go. + +` diff --git a/internal/reflect/append_map_test.go b/internal/reflect/append_map_test.go new file mode 100644 index 0000000..2ee718f --- /dev/null +++ b/internal/reflect/append_map_test.go @@ -0,0 +1,63 @@ +/* + * Copyright 2024 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package reflect + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestAppendMapAnyAny(t *testing.T) { + var funcs map[struct{ k, v ttype }]appendFuncType + funcs, mapAppendFuncs = mapAppendFuncs, nil // reset mapAppendFuncs to use appendMapAnyAny + defer func() { + mapAppendFuncs = funcs + }() + + type EnumKey int64 + type EnumType int64 + type EmptyStruct struct{} + + type TestStruct struct { + M1 map[EnumKey]int8 `frugal:"1,optional,map"` + M2 map[EnumKey]int16 `frugal:"2,optional,map"` + M3 map[EnumKey]int32 `frugal:"3,optional,map"` + M4 map[EnumKey]int64 `frugal:"4,optional,map"` + M5 map[EnumKey]EnumType `frugal:"5,optional,map"` + M6 map[EnumKey]string `frugal:"6,optional,map"` + M7 map[EnumKey]*EmptyStruct `frugal:"7,optional,map"` + } + + p0 := &TestStruct{ + M1: map[EnumKey]int8{11: 1, 12: 2}, + M2: map[EnumKey]int16{21: 1, 22: 2}, + M3: map[EnumKey]int32{31: 1, 32: 2}, + M4: map[EnumKey]int64{41: 1, 42: 2}, + M5: map[EnumKey]EnumType{51: 1, 52: 2}, + M6: map[EnumKey]string{61: "1", 62: "2"}, + M7: map[EnumKey]*EmptyStruct{71: {}, 72: {}}, + } + + b, err := Append(nil, p0) + require.NoError(t, err) + + p1 := &TestStruct{} + _, err = Decode(b, p1) + require.NoError(t, err) + require.Equal(t, p0, p1) +} diff --git a/internal/reflect/append_test.go b/internal/reflect/append_test.go new file mode 100644 index 0000000..335876d --- /dev/null +++ b/internal/reflect/append_test.go @@ -0,0 +1,54 @@ +/* + * Copyright 2024 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package reflect + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestAppendStruct(t *testing.T) { + type EnumType int64 + type TestStruct struct { + F11 *int8 `frugal:"11,optional,i8"` + F12 *bool `frugal:"12,optional,bool"` + + F2 *int16 `frugal:"2,optional,i16"` + F3 *int32 `frugal:"3,optional,i32"` + F4 *int64 `frugal:"4,optional,i64"` + F5 *EnumType `frugal:"5,optional,EnumType"` + F6 *string `frugal:"6,optional,string"` + } + + p0 := &TestStruct{ + F11: P(int8(1)), + F12: P(false), + F2: P(int16(2)), + F3: P(int32(3)), + F4: P(int64(4)), + F5: P(EnumType(5)), + F6: P("6"), + } + + b, err := Append(nil, p0) + require.NoError(t, err) + p1 := &TestStruct{} + _, err = Decode(b, p1) + require.NoError(t, err) + require.Equal(t, p0, p1) +} diff --git a/internal/reflect/decoder_test.go b/internal/reflect/decoder_test.go index 12ebb63..f4c5311 100644 --- a/internal/reflect/decoder_test.go +++ b/internal/reflect/decoder_test.go @@ -50,6 +50,11 @@ func TestDecode(t *testing.T) { } testcases := []testcase{ + { + name: "case_default", + update: func(p0 *TestTypes) {}, + test: func(t *testing.T, p1 *TestTypes) {}, + }, { name: "case_bool", update: func(p0 *TestTypes) { p0.FBool = true }, @@ -179,10 +184,10 @@ func TestDecode(t *testing.T) { p0 := NewTestTypes() updatef(p0) // update by testcase func - b := make([]byte, EncodedSize(p0)) - n, err := Encode(b, p0) + n := EncodedSize(p0) + b, err := Append(nil, p0) require.NoError(t, err) - require.Equal(t, len(b), n) + require.Equal(t, n, len(b)) // verify by gopkg thrift n, err = thrift.Binary.Skip(b, thrift.TType(tSTRUCT)) @@ -281,10 +286,10 @@ func TestDecodeOptional(t *testing.T) { p0 := NewTestTypesOptional() updatef(p0) // update by testcase func - b := make([]byte, EncodedSize(p0)) - n, err := Encode(b, p0) + n := EncodedSize(p0) + b, err := Append(nil, p0) require.NoError(t, err) - require.Equal(t, len(b), n) + require.Equal(t, n, len(b)) // verify by gopkg thrift n, err = thrift.Binary.Skip(b, thrift.TType(tSTRUCT)) @@ -310,20 +315,17 @@ func TestDecodeRequired(t *testing.T) { } v := true p0 := &S0{} - b := make([]byte, 10) - n, err := Encode(b, p0) + b, err := Append(nil, p0) require.NoError(t, err) - b = b[:n] p1 := &S1{} _, err = Decode(b, p1) require.Equal(t, newRequiredFieldNotSetException("V"), err) p0.V = &v - n, err = Encode(b[:10], p0) + b, err = Append(nil, p0) require.NoError(t, err) - b = b[:n] - n, err = Decode(b, p1) + n, err := Decode(b, p1) require.NoError(t, err) require.Equal(t, len(b), n) require.Equal(t, true, p1.V) @@ -333,31 +335,26 @@ func TestDecodeUnknownFields(t *testing.T) { type Msg0 struct { I0 int32 `thrift:"i0,2" frugal:"2,default,i32"` S0 string `thrift:"s0,3" frugal:"3,default,string"` - S1 string `thrift:"s1,4" frugal:"4,default,string"` - I1 int32 `thrift:"i1,5" frugal:"5,default,i32"` } - type Msg1 struct { // without S0, I1 - I0 int32 `thrift:"i0,2" frugal:"2,default,i32"` - S1 string `thrift:"s1,4" frugal:"4,default,string"` + type Msg1 struct { // without S0 + I0 int32 `thrift:"i0,2" frugal:"2,default,i32"` _unknownFields []byte } - msg := Msg0{I0: 1, S0: "s0", S1: "s1", I1: 2} + msg := Msg0{I0: 1, S0: "s0"} b := make([]byte, EncodedSize(msg)) - _, _ = Encode(b, msg) + _, _ = Append(b[:0], msg) p := &Msg1{} _, _ = Decode(b, p) assert.Equal(t, msg.I0, p.I0) - assert.Equal(t, msg.S1, p.S1) - sz := fieldHeaderLen + strHeaderLen + len(msg.S0) + fieldHeaderLen + 4 + sz := fieldHeaderLen + strHeaderLen + len(msg.S0) testb := make([]byte, sz) testb = appendStringField(testb[:0], 3, msg.S0) - testb = appendInt32Field(testb, 5, uint32(msg.I1)) assert.Equal(t, sz, len(testb)) assert.Equal(t, testb, p._unknownFields) } diff --git a/internal/reflect/desc.go b/internal/reflect/desc.go index cd6801e..8f99cda 100644 --- a/internal/reflect/desc.go +++ b/internal/reflect/desc.go @@ -168,6 +168,10 @@ func newStructDesc(t reflect.Type) (*structDesc, error) { return d, nil } +func (d *structDesc) Name() string { + return d.rt.String() +} + func (d *structDesc) GetField(fid uint16) *tField { if fid > d.maxID { return nil diff --git a/internal/reflect/encoder.go b/internal/reflect/encoder.go deleted file mode 100644 index b202fc7..0000000 --- a/internal/reflect/encoder.go +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright 2024 CloudWeGo Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package reflect - -import ( - "encoding/binary" - "errors" - "unsafe" -) - -type tEncoder struct { - // ... -} - -// Encode encodes a struct to buf. -// base is the pointer of the struct -func (e *tEncoder) Encode(b []byte, base unsafe.Pointer, sd *structDesc) (int, error) { - if base == nil { - // kitex will encode nil struct with a single byte tSTOP - b[0] = byte(tSTOP) - return 1, nil - } - i := 0 - for _, f := range sd.fields { - t := f.Type - p := unsafe.Add(base, f.Offset) - if f.CanSkipEncodeIfNil && *(*unsafe.Pointer)(p) == nil { - continue - } - if f.CanSkipIfDefault && t.Equal(f.Default, p) { - continue - } - - // field header - b[i] = byte(t.WT) - binary.BigEndian.PutUint16(b[i+1:], f.ID) - i += fieldHeaderLen - - // field value - if t.SimpleType { // fast path - if t.IsPointer { - p = *(*unsafe.Pointer)(p) - } - i += encodeSimpleTypes(t.T, b[i:], p) - } else if t.T == tSTRUCT { - // tSTRUCT always is pointer? - n, err := e.Encode(b[i:], *(*unsafe.Pointer)(p), t.Sd) - if err != nil { - return i, err - } - i += n - } else { - n, err := e.encodeContainerType(t, b[i:], p) - if err != nil { - return i, err - } - i += n - } - } - if sd.hasUnknownFields { - xb := *(*[]byte)(unsafe.Add(base, sd.unknownFieldsOffset)) - if len(xb) > 0 { - i += copy(b[i:], xb) - } - } - b[i] = byte(tSTOP) - i++ - return i, nil -} - -func (e *tEncoder) encodeContainerType(t *tType, b []byte, p unsafe.Pointer) (int, error) { - switch t.T { - case tMAP: - kt := t.K - vt := t.V - // map header - b[0] = byte(kt.WT) - b[1] = byte(vt.WT) - if *(*unsafe.Pointer)(p) == nil { - b[5], b[4], b[3], b[2] = 0, 0, 0, 0 - return mapHeaderLen, nil - } - binary.BigEndian.PutUint32(b[2:], uint32(maplen(*(*unsafe.Pointer)(p)))) - i := mapHeaderLen - it := newMapIter(rvWithPtr(t.RV, p)) - for kp, vp := it.Next(); kp != nil; kp, vp = it.Next() { - // Key - // SimpleType or tSTRUCT - if kt.SimpleType { // fast path - i += encodeSimpleTypes(kt.T, b[i:], kp) - } else { - n, err := e.Encode(b[i:], *(*unsafe.Pointer)(kp), kt.Sd) - if err != nil { - return i, err - } - i += n - } - - // Value - if vt.SimpleType { // fast path - i += encodeSimpleTypes(vt.T, b[i:], vp) - } else if vt.T == tSTRUCT { - // tSTRUCT always is pointer? - n, err := e.Encode(b[i:], *(*unsafe.Pointer)(vp), vt.Sd) - if err != nil { - return i, err - } - i += n - } else { // tLIST, tSET, tMAP, unlikely... - n, err := e.encodeContainerType(vt, b[i:], vp) - if err != nil { - return i, err - } - i += n - } - } - return i, nil - case tLIST, tSET: // NOTE: for tSET, it may be map in the future - // list header - vt := t.V - b[0] = byte(vt.WT) - if *(*unsafe.Pointer)(p) == nil { - b[4], b[3], b[2], b[1] = 0, 0, 0, 0 - return listHeaderLen, nil - } - h := (*sliceHeader)(p) - if t.T == tSET { // for tSET, check duplicated items - if err := checkUniqueness(t.V, h); err != nil { - return listHeaderLen, err - } - } - binary.BigEndian.PutUint32(b[1:], uint32(h.Len)) - i := listHeaderLen - vp := h.UnsafePointer() - // list elements - for j := 0; j < h.Len; j++ { - if j != 0 { - vp = unsafe.Add(vp, vt.Size) // move to next element - } - if vt.SimpleType { // fast path - i += encodeSimpleTypes(vt.T, b[i:], vp) - } else if vt.T == tSTRUCT { - // tSTRUCT always is pointer? - n, err := e.Encode(b[i:], *(*unsafe.Pointer)(vp), vt.Sd) - if err != nil { - return i, err - } - i += n - } else { // tLIST, tSET, tMAP, unlikely... - n, err := e.encodeContainerType(vt, b[i:], vp) - if err != nil { - return i, err - } - i += n - } - } - - return i, nil - } - return 0, errors.New("unknown type") -} - -// NOTE: PLEASE ADD CODE CAREFULLY -// can inline encodeSimpleTypes with cost 78 (budget 80) -func encodeSimpleTypes(t ttype, b []byte, p unsafe.Pointer) int { - switch t { - case tBYTE, tBOOL: - b[0] = *((*byte)(p)) // for tBOOL, true -> 1, false -> 0 - return 1 - case tI16: - binary.BigEndian.PutUint16(b, uint16(*((*int16)(p)))) - return 2 - case tI32: - binary.BigEndian.PutUint32(b, uint32(*((*int32)(p)))) - return 4 - case tENUM: - binary.BigEndian.PutUint32(b, uint32(*((*int64)(p)))) - return 4 - case tI64, tDOUBLE: - binary.BigEndian.PutUint64(b, *((*uint64)(p))) - return 8 - case tSTRING: - x := *((*string)(p)) - binary.BigEndian.PutUint32(b, uint32(len(x))) - return 4 + copy(b[4:], x) - } - panic("bug") -} diff --git a/internal/reflect/encoder_test.go b/internal/reflect/encoder_test.go index 5cefd7d..5cc0b26 100644 --- a/internal/reflect/encoder_test.go +++ b/internal/reflect/encoder_test.go @@ -112,9 +112,9 @@ func TestEncode(t *testing.T) { p := NewTestTypesOptional() tc.update(p) assert.Equal(t, tc.expect, EncodedSize(p)) - n, err := Encode(b, p) + x, err := Append(b[:0], p) if assert.NoError(t, err) { - assert.Equal(t, tc.expect, n) + assert.Equal(t, tc.expect, len(x)) } }) } @@ -123,10 +123,9 @@ func TestEncode(t *testing.T) { func TestEncodeStructOther(t *testing.T) { assert.Equal(t, encodedMsgSize, EncodedSize(Msg{})) // indirect type assert.Equal(t, 1, EncodedSize((*Msg)(nil))) // nil - b := make([]byte, encodedMsgSize) - n, err := Encode(b, Msg{}) + b, err := Append(nil, Msg{}) assert.NoError(t, err) - assert.Equal(t, encodedMsgSize, n) + assert.Equal(t, encodedMsgSize, len(b)) } func TestEncodeUnknownFields(t *testing.T) { @@ -140,9 +139,9 @@ func TestEncodeUnknownFields(t *testing.T) { m := &Msg1{I0: 123, S1: "Hello"} m._unknownFields = []byte("helloworld") - b := make([]byte, EncodedSize(m)) - i, err := Encode(b, m) + n := EncodedSize(m) + b, err := Append(nil, m) require.NoError(t, err) - assert.Equal(t, i, len(b)) + assert.Equal(t, n, len(b)) assert.Contains(t, string(b), string(append([]byte("helloworld")[:], byte(tSTOP)))) } diff --git a/internal/reflect/reflect.go b/internal/reflect/reflect.go index e1b6b34..7e9e347 100644 --- a/internal/reflect/reflect.go +++ b/internal/reflect/reflect.go @@ -56,14 +56,16 @@ func EncodedSize(v interface{}) int { return n } -func Encode(b []byte, v interface{}) (n int, err error) { +func Append(b []byte, v interface{}) ([]byte, error) { panicIfHackErr() + + var err error rv := reflect.ValueOf(v) sd := getStructDesc(rv) // copy get and create funcs here for inlining if sd == nil { sd, err = createStructDesc(rv) if err != nil { - return 0, err + return b, err } } // get underlying pointer @@ -79,8 +81,7 @@ func Encode(b []byte, v interface{}) (n int, err error) { // it checks in createStructDesc p = rvPtr(rv) } - e := tEncoder{} - return e.Encode(b, p, sd) + return appendStruct(&tType{Sd: sd}, b, p) } func Decode(b []byte, v interface{}) (int, error) { diff --git a/internal/reflect/reflect_test.go b/internal/reflect/reflect_test.go index 9e6754c..ae977c2 100644 --- a/internal/reflect/reflect_test.go +++ b/internal/reflect/reflect_test.go @@ -81,13 +81,13 @@ func appendStringField(b []byte, fid uint16, s string) []byte { return append(b, s...) } -func BenchmarkEncode(b *testing.B) { +func BenchmarkAppend(b *testing.B) { p := initTestTypesForBenchmark() n := EncodedSize(p) - buf := make([]byte, n) + buf := make([]byte, 0, n) b.SetBytes(int64(n)) for i := 0; i < b.N; i++ { - _, _ = Encode(buf, p) + _, _ = Append(buf, p) } } @@ -107,9 +107,10 @@ func BenchmarkDecode(b *testing.B) { if n <= 0 { b.Fatal(n) } + var err error buf := make([]byte, n) b.SetBytes(int64(n)) - _, err := Encode(buf, p) + buf, err = Append(buf[:0], p) require.NoError(b, err) p0 := NewTestTypesForBenchmark() diff --git a/internal/reflect/ttype.go b/internal/reflect/ttype.go index b8abeeb..717d3d6 100644 --- a/internal/reflect/ttype.go +++ b/internal/reflect/ttype.go @@ -62,7 +62,7 @@ var t2s = [256]string{ tMAP: "MAP", tSET: "SET", tLIST: "LIST", - // no tENUM, it's NOT a wiretype + tENUM: "ENUM", } func ttype2str(t ttype) string { @@ -84,6 +84,8 @@ var simpleTypes = [256]bool{ tSTRING: true, } +type appendFuncType func(t *tType, b []byte, p unsafe.Pointer) ([]byte, error) + type tType struct { T ttype K *tType @@ -113,6 +115,7 @@ type tType struct { // for tLIST, tSET, tMAP, tSTRUCT EncodedSizeFunc func(p unsafe.Pointer) (int, error) + AppendFunc appendFuncType // tMAP only MapTmpVarsPool *sync.Pool // for decoder tmp vars @@ -194,6 +197,16 @@ func newTType(x *defs.Type) *tType { if x.V != nil { t.V = newTType(x.V) } + switch t.T { + case tLIST, tSET: + updateListAppendFunc(t) + case tMAP: + updateMapAppendFunc(t) + case tSTRUCT: + t.AppendFunc = appendStruct + default: + t.AppendFunc = appendAny + } if t.IsPointer && t.V.IsPointer { // XXX: make it simple... not support it, it's not common // The code is not generated by thriftgo? diff --git a/internal/reflect/utils.go b/internal/reflect/utils.go index b828727..5f280ec 100644 --- a/internal/reflect/utils.go +++ b/internal/reflect/utils.go @@ -50,6 +50,10 @@ func lookupFieldName(rt reflect.Type, offset uintptr) string { return "unknown" } +func withFieldErr(err error, sd *structDesc, f *tField) error { + return fmt.Errorf("%q field %d err: %w", sd.Name(), f.ID, err) +} + func checkUniqueness(t *tType, h *sliceHeader) error { var uniq bool switch t.T { @@ -201,3 +205,32 @@ func initOrGetMapTmpVarsPool(t *tType) *sync.Pool { }, } } + +func appendUint16(b []byte, v uint16) []byte { + return append(b, + byte(v>>8), + byte(v), + ) +} + +func appendUint32(b []byte, v uint32) []byte { + return append(b, + byte(v>>24), + byte(v>>16), + byte(v>>8), + byte(v), + ) +} + +func appendUint64(b []byte, v uint64) []byte { + return append(b, + byte(v>>56), + byte(v>>48), + byte(v>>40), + byte(v>>32), + byte(v>>24), + byte(v>>16), + byte(v>>8), + byte(v), + ) +} diff --git a/internal/reflect/utils_test.go b/internal/reflect/utils_test.go index 636c6fc..ff86ab2 100644 --- a/internal/reflect/utils_test.go +++ b/internal/reflect/utils_test.go @@ -24,6 +24,8 @@ import ( "github.com/stretchr/testify/assert" ) +func P[T any](v T) *T { return &v } + func TestCheckUniqueness(t *testing.T) { { // tBOOL typ := &tType{T: tBOOL, RT: reflect.TypeOf(bool(true))} diff --git a/tests/allsize_test.go b/tests/allsize_test.go index 6670c32..28ad34c 100644 --- a/tests/allsize_test.go +++ b/tests/allsize_test.go @@ -273,15 +273,16 @@ func BenchmarkAllSize_Marshal_Frugal_Reflect(b *testing.B) { b.Run(s.name, func(b *testing.B) { b.SetBytes(int64(len(s.bytes))) v := s.val - buf := make([]byte, freflect.EncodedSize(v)) - n, err := freflect.Encode(buf, v) + n := freflect.EncodedSize(v) + buf, err := freflect.Append(make([]byte, 0, n), v) require.NoError(b, err) require.Equal(b, len(buf), n) assert.Equal(b, len(s.bytes), n) + buf = buf[:0] b.ResetTimer() for i := 0; i < b.N; i++ { _ = freflect.EncodedSize(v) - _, _ = freflect.Encode(buf, v) + _, _ = freflect.Append(buf, v) } }) } @@ -431,10 +432,10 @@ func BenchmarkAllSize_Parallel_Marshal_Frugal_Reflect(b *testing.B) { b.ResetTimer() b.RunParallel(func(pb *testing.PB) { v := s.val - buf := make([]byte, freflect.EncodedSize(v)) + buf := make([]byte, 0, freflect.EncodedSize(v)) for pb.Next() { freflect.EncodedSize(v) - _, _ = freflect.Encode(buf, v) + _, _ = freflect.Append(buf, v) } }) })