diff --git a/encoder/assembler_amd64.go b/encoder/assembler_amd64.go index d25c60b28..471bd0d49 100644 --- a/encoder/assembler_amd64.go +++ b/encoder/assembler_amd64.go @@ -232,8 +232,6 @@ var _OpFuncTab = [256]func(*_Assembler, *_Instr) { _OP_is_zero_4 : (*_Assembler)._asm_OP_is_zero_4, _OP_is_zero_8 : (*_Assembler)._asm_OP_is_zero_8, _OP_is_zero_map : (*_Assembler)._asm_OP_is_zero_map, - _OP_is_zero_mem : (*_Assembler)._asm_OP_is_zero_mem, - _OP_is_zero_safe : (*_Assembler)._asm_OP_is_zero_safe, _OP_goto : (*_Assembler)._asm_OP_goto, _OP_map_iter : (*_Assembler)._asm_OP_map_iter, _OP_map_stop : (*_Assembler)._asm_OP_map_stop, @@ -698,79 +696,6 @@ func (self *_Assembler) encode_string(doubleQuote bool) { } } -/** Zero Value Check Routine **/ - -func (self *_Assembler) check_zero(nb int, dest int) { - i := int64(0) - e := int64(nb) - - /* special case: zero-sized value, always empty */ - if e == 0 { - return - } - - /* 32-byte test */ - for i <= e - 32 { - self.Emit("VMOVDQU", jit.Ptr(_SP_p, i), _Y0) // VMOVDQU (SP.p), Y0 - self.Emit("VPTEST" , _Y0, _Y0) // VPTEST Y0, Y0 - self.Sjmp("JNZ" , "_not_zero_z_{n}") // JNZ _not_zero_z_{n} - i += 32 - } - - /* VZEROUPPER to avoid AVX-SSE transition penalty */ - if e >= 32 { - self.Emit("VZEROUPPER") - } - - /* 16-byte test */ - if i <= e - 16 { - self.Emit("MOVOU", jit.Ptr(_SP_p, i), _X0) // MOVOU (SP.p), X0 - self.Emit("PTEST", _X0, _X0) // PTEST X0, X0 - self.Sjmp("JNZ" , "_not_zero_{n}") // JNZ _not_zero_{n} - i += 16 - } - - /* 8-byte test */ - if i <= e - 8 { - self.Emit("CMPQ", jit.Ptr(_SP_p, i), jit.Imm(0)) // CMPQ i(SP.p), $0 - self.Sjmp("JNE" , "_not_zero_{n}") // JNE _not_zero_{n} - i += 8 - } - - /* 4 byte test */ - if i <= e - 4 { - self.Emit("CMPL", jit.Ptr(_SP_p, i), jit.Imm(0)) // CMPL i(SP.p), $0 - self.Sjmp("JNE" , "_not_zero_{n}") // JNE _not_zero_{n} - i += 4 - } - - /* 2 byte test */ - if i <= e - 2 { - self.Emit("CMPW", jit.Ptr(_SP_p, i), jit.Imm(0)) // CMPW i(SP.p), $0 - self.Sjmp("JNE" , "_not_zero_{n}") // JNE _not_zero_{n} - i += 2 - } - - /* the last byte */ - if i < e { - self.Emit("CMPB", jit.Ptr(_SP_p, i), jit.Imm(0)) // CMPB i(SP.p), $0 - self.Sjmp("JNE" , "_not_zero_{n}") // JNE _not_zero_{n} - } - - /* value is not zero */ - if e < 32 { - self.Xjmp("JMP", dest) - self.Link("_not_zero_{n}") - return - } - - /* VZEROUPPER to avoid AVX-SSE transition penalty */ - self.Xjmp("JMP", dest) - self.Link("_not_zero_z_{n}") - self.Emit("VZEROUPPER") - self.Link("_not_zero_{n}") -} - /** OpCode Assembler Functions **/ var ( @@ -787,7 +712,6 @@ var ( var ( _F_memmove = jit.Func(memmove) - _F_isZeroTyped = jit.Func(isZeroTyped) _F_error_number = jit.Func(error_number) _F_isValidNumber = jit.Func(isValidNumber) ) @@ -1092,20 +1016,6 @@ func (self *_Assembler) _asm_OP_is_zero_map(p *_Instr) { self.Xjmp("JE" , p.vi()) // JE p.vi() } -func (self *_Assembler) _asm_OP_is_zero_mem(p *_Instr) { - self.check_zero(p.vlen(), p.vi()) -} - -func (self *_Assembler) _asm_OP_is_zero_safe(p *_Instr) { - self.check_zero(p.vlen(), p.vi()) // CHECKZ $p.vlen(), p.vi() - self.Emit("MOVQ", jit.Type(p.vt()), _AX) // MOVQ $p.vt(), AX - self.Emit("MOVQ", _SP_p, jit.Ptr(_SP, 0)) // MOVQ SP.p, (SP) - self.Emit("MOVQ", _AX, jit.Ptr(_SP, 8)) // MOVQ AX, 8(SP) - self.call_go(_F_isZeroTyped) // CALL_GO isZeroTyped - self.Emit("CMPQ", jit.Ptr(_SP, 16), jit.Imm(0)) // CMPQ 16(SP), $0 - self.Xjmp("JNE" , p.vi()) // JNE p.vi() -} - func (self *_Assembler) _asm_OP_goto(p *_Instr) { self.Xjmp("JMP", p.vi()) } diff --git a/encoder/compiler.go b/encoder/compiler.go index 8f3348291..56358d726 100644 --- a/encoder/compiler.go +++ b/encoder/compiler.go @@ -64,8 +64,6 @@ const ( _OP_is_zero_4 _OP_is_zero_8 _OP_is_zero_map - _OP_is_zero_mem - _OP_is_zero_safe _OP_goto _OP_map_iter _OP_map_stop @@ -127,8 +125,6 @@ var _OpNames = [256]string { _OP_is_zero_4 : "is_zero_4", _OP_is_zero_8 : "is_zero_8", _OP_is_zero_map : "is_zero_map", - _OP_is_zero_mem : "is_zero_mem", - _OP_is_zero_safe : "is_zero_safe", _OP_goto : "goto", _OP_map_iter : "map_iter", _OP_map_stop : "map_stop", @@ -259,8 +255,6 @@ func (self _Instr) isBranch() bool { case _OP_is_zero_2 : fallthrough case _OP_is_zero_4 : fallthrough case _OP_is_zero_8 : fallthrough - case _OP_is_zero_mem : fallthrough - case _OP_is_zero_safe : fallthrough case _OP_map_check_key : fallthrough case _OP_map_write_key : fallthrough case _OP_slice_next : fallthrough @@ -291,8 +285,6 @@ func (self _Instr) disassemble() string { case _OP_cond_testc : fallthrough case _OP_map_check_key : fallthrough case _OP_map_write_key : return fmt.Sprintf("%-18sL_%d", self.op().String(), self.vi()) - case _OP_is_zero_mem : fallthrough - case _OP_is_zero_safe : fallthrough case _OP_slice_next : return fmt.Sprintf("%-18sL_%d, %s", self.op().String(), self.vi(), self.vt()) default : return self.op().String() } @@ -686,7 +678,7 @@ func (self *_Compiler) compileStructBody(p *_Program, sp int, vt reflect.Type) { } /* check for "omitempty" option */ - if (fv.Opts & resolver.F_omitempty) != 0 { + if fv.Type.Kind() != reflect.Struct && fv.Type.Kind() != reflect.Array && (fv.Opts & resolver.F_omitempty) != 0 { s = append(s, p.pc()) self.compileStructFieldZero(p, fv.Type) } @@ -799,7 +791,6 @@ func (self *_Compiler) compileStructFieldZero(p *_Program, vt reflect.Type) { case reflect.Map : p.add(_OP_is_zero_map) case reflect.Ptr : p.add(_OP_is_nil) case reflect.Slice : p.add(_OP_is_nil_p1) - case reflect.Struct : self.compileStructFieldNonTrivialZero(p, vt) default : panic(error_type(vt)) } } @@ -810,14 +801,6 @@ func (self *_Compiler) compileStructFieldQuoted(p *_Program, sp int, vt reflect. p.int(_OP_byte, '"') } -func (self *_Compiler) compileStructFieldNonTrivialZero(p *_Program, vt reflect.Type) { - if isTrivialZeroable(vt) { - p.rtt(_OP_is_zero_mem, vt) - } else { - p.rtt(_OP_is_zero_safe, vt) - } -} - func (self *_Compiler) compileInterface(p *_Program, vt reflect.Type) { x := p.pc() p.add(_OP_is_nil_p1) diff --git a/encoder/primitives.go b/encoder/primitives.go index b6856191c..8b0e288dc 100644 --- a/encoder/primitives.go +++ b/encoder/primitives.go @@ -19,7 +19,6 @@ package encoder import ( `encoding` `encoding/json` - `reflect` `unsafe` `github.com/bytedance/sonic/internal/native` @@ -92,74 +91,4 @@ func encodeTextMarshaler(buf *[]byte, val encoding.TextMarshaler) error { } else { return encodeString(buf, rt.Mem2Str(ret)) } -} - -func isZeroSafe(p unsafe.Pointer, vt *rt.GoType) bool { - if native.Lzero(p, int(vt.Size)) == 0 { - return true - } else { - return isZeroTyped(p, vt) - } -} - -func isZeroTyped(p unsafe.Pointer, vt *rt.GoType) bool { - switch vt.Kind() { - case reflect.Map : return (*(**rt.GoMap)(p)).Count == 0 - case reflect.Slice : return (*rt.GoSlice)(p).Len == 0 - case reflect.String : return (*rt.GoString)(p).Len == 0 - case reflect.Struct : return isZeroStruct(p, vt) - case reflect.Interface : return (*rt.GoEface)(p).Value == nil - default : return false - } -} - -func isZeroStruct(p unsafe.Pointer, vt *rt.GoType) bool { - var dp uintptr - var fp unsafe.Pointer - - /* check for each field */ - for _, fv := range (*rt.GoStructType)(unsafe.Pointer(vt)).Fields { - dp = fv.OffEmbed >> 1 - fp = unsafe.Pointer(uintptr(p) + dp) - - /* check for the field */ - if !isZeroSafe(fp, fv.Type) { - return false - } - } - - /* all tests are passed */ - return true -} - -func isTrivialZeroable(vt reflect.Type) bool { - switch vt.Kind() { - case reflect.Bool : return true - case reflect.Int : return true - case reflect.Int8 : return true - case reflect.Int16 : return true - case reflect.Int32 : return true - case reflect.Int64 : return true - case reflect.Uint : return true - case reflect.Uint8 : return true - case reflect.Uint16 : return true - case reflect.Uint32 : return true - case reflect.Uint64 : return true - case reflect.Uintptr : return true - case reflect.Float32 : return true - case reflect.Float64 : return true - case reflect.String : return false - case reflect.Array : return true - case reflect.Interface : return false - case reflect.Map : return false - case reflect.Ptr : return true - case reflect.Slice : return false - case reflect.Struct : return isStructTrivialZeroable(vt) - default : return false - } -} - -func isStructTrivialZeroable(vt reflect.Type) bool { - for i := 0; i < vt.NumField(); i++ { if !isTrivialZeroable(vt.Field(i).Type) { return false } } - return true -} +} \ No newline at end of file diff --git a/issue_test/issue113_test.go b/issue_test/issue113_test.go new file mode 100644 index 000000000..adf47ba57 --- /dev/null +++ b/issue_test/issue113_test.go @@ -0,0 +1,160 @@ +/* + * Copyright 2021 ByteDance Inc. + * + * 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 issue_test + +import ( + `testing` + + `github.com/bytedance/sonic/encoder` + `github.com/stretchr/testify/require` +) + +type Issue113_OmitemptyOpt struct { + Bool bool `json:"bool,omitempty"` + Int int `json:"int,omitempty"` + Int8 int8 `json:"int8,omitempty"` + Int16 int16 `json:"int16,omitempty"` + Int32 int32 `json:"int32,omitempty"` + Int64 int64 `json:"int64,omitempty"` + Uint uint `json:"uint,omitempty"` + Uint8 uint8 `json:"uint8,omitempty"` + Uint16 uint16 `json:"uint16,omitempty"` + Uint32 uint32 `json:"uint32,omitempty"` + Uint64 uint64 `json:"uint64,omitempty"` + Float32 float32 `json:"float32,omitempty"` + Float64 float64 `json:"float64,omitempty"` + Uintptr uintptr `json:"uintptr,omitempty"` + String string `json:"string,omitempty"` + Array0 [0]uint `json:"array0,omitempty"` + Array [2]int `json:"array,omitempty"` + Interface interface{} `json:"interface,omitempty"` + Map0 map[int]interface{} `json:"map0,omitempty"` + Map map[string]float64 `json:"map,omitempty"` + Slice0 []int `json:"slice0,omitempty"` + Slice []byte `json:"slice,omitempty"` + Ptr * Issue113_Inner `json:"ptr,omitempty"` + Struct1 Issue113_Inner `json:"struct1,omitempty"` + Struct2 struct{} `json:"struct2,omitempty"` +} + +type Issue113_Inner struct { + S string `json:"s,"` + So string `json:"so,omitempty"` +} + +var issue13ExpectedEmptyOpt = `{ + "array": [ + 0, + 0 + ], + "struct1": { + "s": "" + }, + "struct2": {} +}` + +var issue13ExpectedNonemptyOpt = `{ + "bool": true, + "int": 1, + "int8": -1, + "int16": 1, + "int32": 2, + "int64": 64, + "uint": 1, + "uint8": 8, + "uint16": 16, + "uint32": 32, + "uint64": 64, + "float32": 1, + "float64": -2.34e64, + "uintptr": 1, + "string": "string", + "array": [ + 0, + -1 + ], + "interface": { + "s": "not omit" + }, + "map0": { + "0": "zero" + }, + "map": { + "key": 0 + }, + "slice0": [ + 0 + ], + "slice": "Yg==", + "ptr": { + "s": "not omit" + }, + "struct1": { + "s": "not omit" + }, + "struct2": {} +}` + +func TestIssue113_MarshalEmptyFieldsWithOmitemptyOpt(t *testing.T) { + var obj Issue113_OmitemptyOpt + obj.Slice0 = make([]int, 0, 100) // empty slice + obj.Map0 = make(map[int]interface{}) // empty map + + got, err := encoder.EncodeIndented(&obj, "", " ", 0) + + require.NoError(t, err) + require.Equal(t, issue13ExpectedEmptyOpt, string(got)) +} + +func TestIssue113_MarshalNonemptyFieldsWithOmitemptyOpt(t *testing.T) { + var inner = & Issue113_Inner { + S : "not omit", + } + + var obj = & Issue113_OmitemptyOpt{ + Bool : true, + Int : 1, + Int8 : -1, + Int16 : 1, + Int32 : 2, + Int64 : 64, + Uint : 1, + Uint8 : 8, + Uint16 : 16, + Uint32 : 32, + Uint64 : 64, + Float32 : 1.0, + Float64 : -2.34e+64, + Uintptr : uintptr(0x1), + String : "string", + Array0 : [0]uint{}, + Array : [2]int{0, -1}, + Interface : *inner, + Map0 : map[int]interface{}{0 : "zero"}, + Map : map[string]float64{"key" : 0.0}, + Slice0 : make([]int, 1, 1), + Slice : []byte("b"), + Ptr : inner, + Struct1 : *inner, + Struct2 : struct{}{}, + } + + got, err := encoder.EncodeIndented(&obj, "", " ", 0) + + require.NoError(t, err) + require.Equal(t, issue13ExpectedNonemptyOpt, string(got)) +} \ No newline at end of file