2
0
Fork 0
mirror of https://github.com/ii64/sonic.git synced 2026-06-21 00:46:43 +08:00

fix:(encoder) pass pv through compiler recursively (#336)

* pass pv

* test reflect indirect

* fix: pass `pv` throught compiler recursively
This commit is contained in:
Yi Duan 2022-12-13 14:26:58 +08:00 committed by GitHub
parent 01c0d36194
commit f421ee8530
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 191 additions and 70 deletions

View file

@ -179,7 +179,7 @@ func Pretouch(vt reflect.Type, opts ...option.CompileOption) error {
func pretouchType(_vt reflect.Type, opts option.CompileOptions) (map[reflect.Type]bool, error) { func pretouchType(_vt reflect.Type, opts option.CompileOptions) (map[reflect.Type]bool, error) {
/* compile function */ /* compile function */
compiler := newCompiler().apply(opts) compiler := newCompiler().apply(opts)
decoder := func(vt *rt.GoType) (interface{}, error) { decoder := func(vt *rt.GoType, _ ...interface{}) (interface{}, error) {
if pp, err := compiler.compile(_vt); err != nil { if pp, err := compiler.compile(_vt); err != nil {
return nil, err return nil, err
} else { } else {

View file

@ -147,7 +147,7 @@ func referenceFields(v *caching.FieldMap) int64 {
return int64(uintptr(unsafe.Pointer(v))) return int64(uintptr(unsafe.Pointer(v)))
} }
func makeDecoder(vt *rt.GoType) (interface{}, error) { func makeDecoder(vt *rt.GoType, _ ...interface{}) (interface{}, error) {
if pp, err := newCompiler().compile(vt.Pack()); err != nil { if pp, err := newCompiler().compile(vt.Pack()); err != nil {
return nil, err return nil, err
} else { } else {

View file

@ -1011,11 +1011,12 @@ func (self *_Assembler) _asm_OP_drop_2(_ *_Instr) {
func (self *_Assembler) _asm_OP_recurse(p *_Instr) { func (self *_Assembler) _asm_OP_recurse(p *_Instr) {
self.prep_buffer() // MOVE {buf}, (SP) self.prep_buffer() // MOVE {buf}, (SP)
self.Emit("MOVQ", jit.Type(p.vt()), _AX) // MOVQ $(type(p.vt())), AX vt, pv := p.vp()
self.Emit("MOVQ", jit.Type(vt), _AX) // MOVQ $(type(p.vt())), AX
self.Emit("MOVQ", _AX, jit.Ptr(_SP, 8)) // MOVQ AX, 8(SP) self.Emit("MOVQ", _AX, jit.Ptr(_SP, 8)) // MOVQ AX, 8(SP)
/* check for indirection */ /* check for indirection */
if (p.vf() & rt.F_direct) != 0 { if !rt.UnpackType(vt).Indirect() {
self.Emit("MOVQ", _SP_p, _AX) // MOVQ SP.p, AX self.Emit("MOVQ", _SP_p, _AX) // MOVQ SP.p, AX
} else { } else {
self.Emit("MOVQ", _SP_p, _VAR_vp) // MOVQ SP.p, 48(SP) self.Emit("MOVQ", _SP_p, _VAR_vp) // MOVQ SP.p, 48(SP)
@ -1026,6 +1027,9 @@ func (self *_Assembler) _asm_OP_recurse(p *_Instr) {
self.Emit("MOVQ" , _AX, jit.Ptr(_SP, 16)) // MOVQ AX, 16(SP) self.Emit("MOVQ" , _AX, jit.Ptr(_SP, 16)) // MOVQ AX, 16(SP)
self.Emit("MOVQ" , _ST, jit.Ptr(_SP, 24)) // MOVQ ST, 24(SP) self.Emit("MOVQ" , _ST, jit.Ptr(_SP, 24)) // MOVQ ST, 24(SP)
self.Emit("MOVQ" , _ARG_fv, _AX) // MOVQ fv, AX self.Emit("MOVQ" , _ARG_fv, _AX) // MOVQ fv, AX
if pv {
self.Emit("BTCQ", jit.Imm(bitPointerValue), _AX) // BTCQ $1, AX
}
self.Emit("MOVQ" , _AX, jit.Ptr(_SP, 32)) // MOVQ AX, 32(SP) self.Emit("MOVQ" , _AX, jit.Ptr(_SP, 32)) // MOVQ AX, 32(SP)
self.call_encoder(_F_encodeTypedPointer) // CALL encodeTypedPointer self.call_encoder(_F_encodeTypedPointer) // CALL encodeTypedPointer
self.Emit("MOVQ" , jit.Ptr(_SP, 40), _ET) // MOVQ 40(SP), ET self.Emit("MOVQ" , jit.Ptr(_SP, 40), _ET) // MOVQ 40(SP), ET

View file

@ -1015,10 +1015,11 @@ func (self *_Assembler) _asm_OP_drop_2(_ *_Instr) {
func (self *_Assembler) _asm_OP_recurse(p *_Instr) { func (self *_Assembler) _asm_OP_recurse(p *_Instr) {
self.prep_buffer_AX() // MOVE {buf}, (SP) self.prep_buffer_AX() // MOVE {buf}, (SP)
self.Emit("MOVQ", jit.Type(p.vt()), _BX) // MOVQ $(type(p.vt())), BX vt, pv := p.vp()
self.Emit("MOVQ", jit.Type(vt), _BX) // MOVQ $(type(p.vt())), BX
/* check for indirection */ /* check for indirection */
if (p.vf() & rt.F_direct) != 0 { if !rt.UnpackType(vt).Indirect() {
self.Emit("MOVQ", _SP_p, _CX) // MOVQ SP.p, CX self.Emit("MOVQ", _SP_p, _CX) // MOVQ SP.p, CX
} else { } else {
self.Emit("MOVQ", _SP_p, _VAR_vp) // MOVQ SP.p, VAR.vp self.Emit("MOVQ", _SP_p, _VAR_vp) // MOVQ SP.p, VAR.vp
@ -1028,6 +1029,9 @@ func (self *_Assembler) _asm_OP_recurse(p *_Instr) {
/* call the encoder */ /* call the encoder */
self.Emit("MOVQ" , _ST, _DI) // MOVQ ST, DI self.Emit("MOVQ" , _ST, _DI) // MOVQ ST, DI
self.Emit("MOVQ" , _ARG_fv, _SI) // MOVQ $fv, SI self.Emit("MOVQ" , _ARG_fv, _SI) // MOVQ $fv, SI
if pv {
self.Emit("BTCQ", jit.Imm(bitPointerValue), _SI) // BTCQ $1, SI
}
self.call_encoder(_F_encodeTypedPointer) // CALL encodeTypedPointer self.call_encoder(_F_encodeTypedPointer) // CALL encodeTypedPointer
self.Emit("TESTQ", _ET, _ET) // TESTQ ET, ET self.Emit("TESTQ", _ET, _ET) // TESTQ ET, ET
self.Sjmp("JNZ" , _LB_error) // JNZ _error self.Sjmp("JNZ" , _LB_error) // JNZ _error

View file

@ -52,7 +52,7 @@ func TestEncoderMemoryCorruption(t *testing.T) {
} }
func TestAssembler_CompileAndLoad(t *testing.T) { func TestAssembler_CompileAndLoad(t *testing.T) {
p, err := newCompiler().compile(reflect.TypeOf((*bool)(nil))) p, err := newCompiler().compile(reflect.TypeOf((*bool)(nil)), true)
assert.Nil(t, err) assert.Nil(t, err)
a := newAssembler(p) a := newAssembler(p)
f := a.Load() f := a.Load()
@ -127,7 +127,7 @@ type RecursiveValue struct {
} }
func mustCompile(t interface{}) _Program { func mustCompile(t interface{}) _Program {
p, err := newCompiler().compile(reflect.TypeOf(t)) p, err := newCompiler().compile(reflect.TypeOf(t), !rt.UnpackEface(t).Type.Indirect())
if err != nil { if err != nil {
panic(err) panic(err)
} }

View file

@ -217,6 +217,17 @@ func newInsVt(op _Op, vt reflect.Type) _Instr {
} }
} }
func newInsVp(op _Op, vt reflect.Type, pv bool) _Instr {
i := 0
if pv {
i = 1
}
return _Instr {
u: packOp(op) | rt.PackInt(i),
p: unsafe.Pointer(rt.UnpackType(vt)),
}
}
func (self _Instr) op() _Op { func (self _Instr) op() _Op {
return _Op(self.u >> 56) return _Op(self.u >> 56)
} }
@ -243,6 +254,10 @@ func (self _Instr) vt() reflect.Type {
return (*rt.GoType)(self.p).Pack() return (*rt.GoType)(self.p).Pack()
} }
func (self _Instr) vp() (vt reflect.Type, pv bool) {
return (*rt.GoType)(self.p).Pack(), rt.UnpackInt(self.u) == 1
}
func (self _Instr) i64() int64 { func (self _Instr) i64() int64 {
return int64(self.vi()) return int64(self.vi())
} }
@ -345,6 +360,10 @@ func (self *_Program) rtt(op _Op, vt reflect.Type) {
*self = append(*self, newInsVt(op, vt)) *self = append(*self, newInsVt(op, vt))
} }
func (self *_Program) vp(op _Op, vt reflect.Type, pv bool) {
*self = append(*self, newInsVp(op, vt, pv))
}
func (self _Program) disassemble() string { func (self _Program) disassemble() string {
nb := len(self) nb := len(self)
tab := make([]bool, nb + 1) tab := make([]bool, nb + 1)
@ -379,21 +398,21 @@ type _Compiler struct {
opts option.CompileOptions opts option.CompileOptions
pv bool pv bool
tab map[reflect.Type]bool tab map[reflect.Type]bool
rec map[reflect.Type]bool rec map[reflect.Type]uint8
} }
func newCompiler() *_Compiler { func newCompiler() *_Compiler {
return &_Compiler { return &_Compiler {
opts: option.DefaultCompileOptions(), opts: option.DefaultCompileOptions(),
tab: map[reflect.Type]bool{}, tab: map[reflect.Type]bool{},
rec: map[reflect.Type]bool{}, rec: map[reflect.Type]uint8{},
} }
} }
func (self *_Compiler) apply(opts option.CompileOptions) *_Compiler { func (self *_Compiler) apply(opts option.CompileOptions) *_Compiler {
self.opts = opts self.opts = opts
if self.opts.RecursiveDepth > 0 { if self.opts.RecursiveDepth > 0 {
self.rec = map[reflect.Type]bool{} self.rec = map[reflect.Type]uint8{}
} }
return self return self
} }
@ -408,15 +427,15 @@ func (self *_Compiler) rescue(ep *error) {
} }
} }
func (self *_Compiler) compile(vt reflect.Type) (ret _Program, err error) { func (self *_Compiler) compile(vt reflect.Type, pv bool) (ret _Program, err error) {
defer self.rescue(&err) defer self.rescue(&err)
self.compileOne(&ret, 0, vt, false) self.compileOne(&ret, 0, vt, pv)
return return
} }
func (self *_Compiler) compileOne(p *_Program, sp int, vt reflect.Type, pv bool) { func (self *_Compiler) compileOne(p *_Program, sp int, vt reflect.Type, pv bool) {
if self.tab[vt] { if self.tab[vt] {
p.rtt(_OP_recurse, vt) p.vp(_OP_recurse, vt, pv)
} else { } else {
self.compileRec(p, sp, vt, pv) self.compileRec(p, sp, vt, pv)
} }
@ -661,9 +680,13 @@ func (self *_Compiler) compileString(p *_Program, vt reflect.Type) {
func (self *_Compiler) compileStruct(p *_Program, sp int, vt reflect.Type) { func (self *_Compiler) compileStruct(p *_Program, sp int, vt reflect.Type) {
if sp >= self.opts.MaxInlineDepth || p.pc() >= _MAX_ILBUF || (sp > 0 && vt.NumField() >= _MAX_FIELDS) { if sp >= self.opts.MaxInlineDepth || p.pc() >= _MAX_ILBUF || (sp > 0 && vt.NumField() >= _MAX_FIELDS) {
p.rtt(_OP_recurse, vt) p.vp(_OP_recurse, vt, self.pv)
if self.opts.RecursiveDepth > 0 { if self.opts.RecursiveDepth > 0 {
self.rec[vt] = true if self.pv {
self.rec[vt] = 1
} else {
self.rec[vt] = 0
}
} }
} else { } else {
self.compileStructBody(p, sp, vt) self.compileStructBody(p, sp, vt)

View file

@ -17,14 +17,34 @@
package encoder package encoder
import ( import (
`reflect` "reflect"
`testing` "testing"
"unsafe"
`github.com/stretchr/testify/assert` "github.com/bytedance/sonic/internal/rt"
"github.com/stretchr/testify/assert"
) )
func TestCompiler_Compile(t *testing.T) { func TestCompiler_Compile(t *testing.T) {
p, err := newCompiler().compile(reflect.TypeOf(_BindingValue)) p, err := newCompiler().compile(reflect.TypeOf(_BindingValue), false)
assert.Nil(t, err) assert.Nil(t, err)
p.disassemble() p.disassemble()
} }
func TestReflectDirect(t *testing.T) {
type A struct {
A int
B int
}
var a A
var b = &a
println("b:", unsafe.Pointer(b))
v := rt.UnpackEface(a)
vv := reflect.ValueOf(a)
_ = vv
println("v:", v.Type.KindFlags, v.Value)
pv := rt.UnpackEface(&a)
pvv := reflect.ValueOf(&a)
_ = pvv
println("pv:", pv.Type.KindFlags, pv.Value)
}

View file

@ -38,6 +38,9 @@ const (
bitCompactMarshaler bitCompactMarshaler
bitNoQuoteTextMarshaler bitNoQuoteTextMarshaler
bitNoNullSliceOrMap bitNoNullSliceOrMap
// used for recursive compile
bitPointerValue = 63
) )
const ( const (
@ -284,47 +287,7 @@ func Pretouch(vt reflect.Type, opts ...option.CompileOption) error {
opt(&cfg) opt(&cfg)
break break
} }
return pretouchRec(map[reflect.Type]bool{vt:true}, cfg) return pretouchRec(map[reflect.Type]uint8{vt: 0}, cfg)
}
func pretouchType(_vt reflect.Type, opts option.CompileOptions) (map[reflect.Type]bool, error) {
/* compile function */
compiler := newCompiler().apply(opts)
encoder := func(vt *rt.GoType) (interface{}, error) {
if pp, err := compiler.compile(_vt); err != nil {
return nil, err
} else {
return newAssembler(pp).Load(), nil
}
}
/* find or compile */
vt := rt.UnpackType(_vt)
if val := programCache.Get(vt); val != nil {
return nil, nil
} else if _, err := programCache.Compute(vt, encoder); err == nil {
return compiler.rec, nil
} else {
return nil, err
}
}
func pretouchRec(vtm map[reflect.Type]bool, opts option.CompileOptions) error {
if opts.RecursiveDepth < 0 || len(vtm) == 0 {
return nil
}
next := make(map[reflect.Type]bool)
for vt := range(vtm) {
sub, err := pretouchType(vt, opts)
if err != nil {
return err
}
for svt := range(sub) {
next[svt] = true
}
}
opts.RecursiveDepth -= 1
return pretouchRec(next, opts)
} }
// Valid validates json and returns first non-blank character position, // Valid validates json and returns first non-blank character position,

View file

@ -21,8 +21,10 @@ import (
`sync` `sync`
`unsafe` `unsafe`
`errors` `errors`
`reflect`
`github.com/bytedance/sonic/internal/caching` `github.com/bytedance/sonic/internal/caching`
`github.com/bytedance/sonic/option`
`github.com/bytedance/sonic/internal/rt` `github.com/bytedance/sonic/internal/rt`
) )
@ -129,20 +131,60 @@ func freeBuffer(p *bytes.Buffer) {
bufferPool.Put(p) bufferPool.Put(p)
} }
func makeEncoder(vt *rt.GoType) (interface{}, error) { func makeEncoder(vt *rt.GoType, ex ...interface{}) (interface{}, error) {
if pp, err := newCompiler().compile(vt.Pack()); err != nil { if pp, err := newCompiler().compile(vt.Pack(), ex[0].(bool)); err != nil {
return nil, err return nil, err
} else { } else {
return newAssembler(pp).Load(), nil return newAssembler(pp).Load(), nil
} }
} }
func findOrCompile(vt *rt.GoType) (_Encoder, error) { func findOrCompile(vt *rt.GoType, pv bool) (_Encoder, error) {
if val := programCache.Get(vt); val != nil { if val := programCache.Get(vt); val != nil {
return val.(_Encoder), nil return val.(_Encoder), nil
} else if ret, err := programCache.Compute(vt, makeEncoder); err == nil { } else if ret, err := programCache.Compute(vt, makeEncoder, pv); err == nil {
return ret.(_Encoder), nil return ret.(_Encoder), nil
} else { } else {
return nil, err return nil, err
} }
} }
func pretouchType(_vt reflect.Type, opts option.CompileOptions, v uint8) (map[reflect.Type]uint8, error) {
/* compile function */
compiler := newCompiler().apply(opts)
encoder := func(vt *rt.GoType, ex ...interface{}) (interface{}, error) {
if pp, err := compiler.compile(_vt, ex[0].(bool)); err != nil {
return nil, err
} else {
return newAssembler(pp).Load(), nil
}
}
/* find or compile */
vt := rt.UnpackType(_vt)
if val := programCache.Get(vt); val != nil {
return nil, nil
} else if _, err := programCache.Compute(vt, encoder, v == 1); err == nil {
return compiler.rec, nil
} else {
return nil, err
}
}
func pretouchRec(vtm map[reflect.Type]uint8, opts option.CompileOptions) error {
if opts.RecursiveDepth < 0 || len(vtm) == 0 {
return nil
}
next := make(map[reflect.Type]uint8)
for vt, v := range vtm {
sub, err := pretouchType(vt, opts, v)
if err != nil {
return err
}
for svt, v := range sub {
next[svt] = v
}
}
opts.RecursiveDepth -= 1
return pretouchRec(next, opts)
}

View file

@ -68,9 +68,9 @@ func encodeString(buf *[]byte, val string) error {
func encodeTypedPointer(buf *[]byte, vt *rt.GoType, vp *unsafe.Pointer, sb *_Stack, fv uint64) error { func encodeTypedPointer(buf *[]byte, vt *rt.GoType, vp *unsafe.Pointer, sb *_Stack, fv uint64) error {
if vt == nil { if vt == nil {
return encodeNil(buf) return encodeNil(buf)
} else if fn, err := findOrCompile(vt); err != nil { } else if fn, err := findOrCompile(vt, (fv&(1<<bitPointerValue)) != 0); err != nil {
return err return err
} else if (vt.KindFlags & rt.F_direct) == 0 { } else if vt.Indirect() {
rt.MoreStack(_FP_size + native.MaxFrameSize) rt.MoreStack(_FP_size + native.MaxFrameSize)
return fn(buf, *vp, sb, fv) return fn(buf, *vp, sb, fv)
} else { } else {

View file

@ -149,7 +149,7 @@ func (self *ProgramCache) Get(vt *rt.GoType) interface{} {
return (*_ProgramMap)(atomic.LoadPointer(&self.p)).get(vt) return (*_ProgramMap)(atomic.LoadPointer(&self.p)).get(vt)
} }
func (self *ProgramCache) Compute(vt *rt.GoType, compute func(*rt.GoType) (interface{}, error)) (interface{}, error) { func (self *ProgramCache) Compute(vt *rt.GoType, compute func(*rt.GoType, ... interface{}) (interface{}, error), ex ...interface{}) (interface{}, error) {
var err error var err error
var val interface{} var val interface{}
@ -163,7 +163,7 @@ func (self *ProgramCache) Compute(vt *rt.GoType, compute func(*rt.GoType) (inter
} }
/* compute the value */ /* compute the value */
if val, err = compute(vt); err != nil { if val, err = compute(vt, ex...); err != nil {
return nil, err return nil, err
} }

View file

@ -36,7 +36,7 @@ func TestPcacheRace(t *testing.T) {
var k = map[string]interface{}{} var k = map[string]interface{}{}
<- start <- start
for i:=0; i<100; i++ { for i:=0; i<100; i++ {
_, _ = pc.Compute(rt.UnpackEface(k).Type, func(_ *rt.GoType) (interface{}, error) { _, _ = pc.Compute(rt.UnpackEface(k).Type, func(*rt.GoType, ... interface{}) (interface{}, error) {
return map[string]interface{}{}, nil return map[string]interface{}{}, nil
}) })
} }

View file

@ -58,6 +58,10 @@ func (self *GoType) String() string {
return self.Pack().String() return self.Pack().String()
} }
func (self *GoType) Indirect() bool {
return self.KindFlags & F_direct == 0
}
type GoMap struct { type GoMap struct {
Count int Count int
Flags uint8 Flags uint8

61
issue_test/issuex_test.go Normal file
View file

@ -0,0 +1,61 @@
package issue_test
import (
"encoding/json"
"fmt"
"reflect"
"strconv"
"testing"
"time"
"github.com/bytedance/sonic"
"github.com/davecgh/go-spew/spew"
"github.com/stretchr/testify/require"
)
func TestPointerValueRecurseMarshal(t *testing.T) {
info := &TestStruct1{
StartTime: JSONTime(time.Now()),
}
infos := &[]*TestStruct1{info}
bytes, err1 := json.Marshal(infos)
fmt.Printf("%+v\n", string(bytes))
spew.Dump(bytes, err1)
jbytes, err2 := sonic.Marshal(infos)
fmt.Printf("%+v\n", string(jbytes))
spew.Dump(jbytes, err2)
require.Equal(t, bytes, jbytes)
}
func TestPointerValueRecursePretouch(t *testing.T) {
info := &TestStruct2{
StartTime: JSONTime(time.Now()),
}
infos := &[]*TestStruct2{info}
bytes, err1 := json.Marshal(infos)
fmt.Printf("%+v\n", string(bytes))
spew.Dump(bytes, err1)
sonic.Pretouch(reflect.TypeOf(infos))
jbytes, err2 := sonic.Marshal(infos)
fmt.Printf("%+v\n", string(jbytes))
spew.Dump(jbytes, err2)
require.Equal(t, bytes, jbytes)
}
type TestStruct1 struct {
StartTime JSONTime
}
type TestStruct2 struct {
StartTime JSONTime
}
type JSONTime time.Time
func (t *JSONTime) MarshalJSON() ([]byte, error) {
return []byte(strconv.FormatInt(time.Time(*t).Unix(), 10)), nil
}