From b7038ed2e0be8e2c5d79d6189990f24f0c0d046b Mon Sep 17 00:00:00 2001 From: zhangming12 Date: Mon, 16 Dec 2024 20:57:55 +0800 Subject: [PATCH] opt: encoder support Uint64ToString --- api.go | 3 + encode_test.go | 116 ++++++++++++++++++ encoder/encoder_native.go | 3 + internal/encoder/alg/opts.go | 3 +- internal/encoder/compiler.go | 8 +- internal/encoder/encoder.go | 3 + internal/encoder/ir/op.go | 38 +++++- internal/encoder/vm/vm.go | 10 ++ .../encoder/x86/assembler_regabi_amd64.go | 15 ++- sonic.go | 3 + 10 files changed, 190 insertions(+), 12 deletions(-) diff --git a/api.go b/api.go index af6be70a4..26cdbaba7 100644 --- a/api.go +++ b/api.go @@ -94,6 +94,9 @@ type Config struct { // Encode Infinity or Nan float into `null`, instead of returning an error. EncodeNullForInfOrNan bool + + // Uint64 into strings on Marshal + Uint64ToString bool } var ( diff --git a/encode_test.go b/encode_test.go index f97244a2c..119085f46 100644 --- a/encode_test.go +++ b/encode_test.go @@ -1224,4 +1224,120 @@ func TestMarshalInfOrNan(t *testing.T) { assert.NotNil(t, err) assert.True(t, strings.Contains(err.Error(), "json: unsupported value: NaN or ±Infinite")) } +} + +func TestUint64ToString(t *testing.T) { + int64ptr := int64(432556670863027541) + uint64ptr := uint64(12372850276778298372) + cases := []struct { + name string + val any + exceptTrue string + exceptFalse string + }{ + { + name: "normal_map", + val: map[string]any{ + "int": int(12), + "int64": int64(34), + "uint64": uint64(56), + }, + exceptTrue: `{"int":12,"int64":34,"uint64":"56"}`, + exceptFalse: `{"int":12,"int64":34,"uint64":56}`, + }, + { + name: "int_key_map", + val: map[int64]any{ + int64(12): int(12), + int64(34): int64(34), + int64(56): uint64(56), + }, + exceptTrue: `{"12":12,"34":34,"56":"56"}`, + exceptFalse: `{"12":12,"34":34,"56":56}`, + }, + { + name: "uint_key_map", + val: map[uint64]any{ + uint64(12): int(12), + uint64(34): int64(34), + uint64(56): uint64(56), + }, + exceptTrue: `{"12":12,"34":34,"56":"56"}`, + exceptFalse: `{"12":12,"34":34,"56":56}`, + }, + { + name: "normal_struct", + val: struct { + Int int `json:"int"` + Int64 int64 `json:"int64"` + Uint64 uint64 `json:"uint64"` + }{ + Int: int(12), + Int64: int64(34), + Uint64: uint64(56), + }, + exceptTrue: `{"int":12,"int64":34,"uint64":"56"}`, + exceptFalse: `{"int":12,"int64":34,"uint64":56}`, + }, + { + name: "normal_slice", + val: []any{ + int(12), int64(34), uint64(56), + }, + exceptTrue: `[12,34,"56"]`, + exceptFalse: `[12,34,56]`, + }, + { + name: "single_int64", + val: int64(34), + exceptTrue: `34`, + exceptFalse: `34`, + }, + { + name: "single_uint64", + val: uint64(56), + exceptTrue: `"56"`, + exceptFalse: `56`, + }, + { + name: "int64ptr", + val: struct { + Map map[string]any + }{map[string]any{"val": struct { + Int64Ptr any + Uint64Ptr any + Int64 any + Uint64 any + }{ + Int64Ptr: &int64ptr, + Uint64Ptr: &uint64ptr, + Int64: int64(123), + Uint64: uint64(456), + }}}, + exceptTrue: `{"Map":{"val":{"Int64Ptr":432556670863027541,` + + `"Uint64Ptr":"12372850276778298372","Int64":123,"Uint64":"456"}}}`, + exceptFalse: `{"Map":{"val":{"Int64Ptr":432556670863027541,` + + `"Uint64Ptr":12372850276778298372,"Int64":123,"Uint64":456}}}`, + }, + } + + check := func(t *testing.T, except string, testRes []byte) { + var tmp1 any + assert.Nil(t, Unmarshal([]byte(testRes), &tmp1)) + var tmp2 any + assert.Nil(t, Unmarshal([]byte(except), &tmp2)) + assert.Equal(t, tmp2, tmp1) + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + b, e := Config{Uint64ToString: true}.Froze().Marshal(c.val) + assert.Nil(t, e) + check(t, c.exceptTrue, b) + + b, e = Config{Uint64ToString: false}.Froze().Marshal(c.val) + assert.Nil(t, e) + check(t, c.exceptFalse, b) + }) + } } \ No newline at end of file diff --git a/encoder/encoder_native.go b/encoder/encoder_native.go index b300ebf08..9eb159e3c 100644 --- a/encoder/encoder_native.go +++ b/encoder/encoder_native.go @@ -73,6 +73,9 @@ const ( // Encode Infinity or Nan float into `null`, instead of returning an error. EncodeNullForInfOrNan Options = encoder.EncodeNullForInfOrNan + + // Uint64 into strings on Marshal + Uint64ToString Options = encoder.Uint64ToString ) diff --git a/internal/encoder/alg/opts.go b/internal/encoder/alg/opts.go index c19e2de4e..8fc94f721 100644 --- a/internal/encoder/alg/opts.go +++ b/internal/encoder/alg/opts.go @@ -25,7 +25,8 @@ const ( BitValidateString BitNoValidateJSONMarshaler BitNoEncoderNewline - BitEncodeNullForInfOrNan + BitEncodeNullForInfOrNan + BitUint64ToString BitPointerValue = 63 ) diff --git a/internal/encoder/compiler.go b/internal/encoder/compiler.go index 902fbc98b..f19894f0d 100644 --- a/internal/encoder/compiler.go +++ b/internal/encoder/compiler.go @@ -179,7 +179,7 @@ func (self *Compiler) compileOps(p *ir.Program, sp int, vt reflect.Type) { case reflect.Bool: p.Add(ir.OP_bool) case reflect.Int: - p.Add(ir.OP_int()) + p.Add(ir.OP_int(), ir.OP_i) case reflect.Int8: p.Add(ir.OP_i8) case reflect.Int16: @@ -189,7 +189,7 @@ func (self *Compiler) compileOps(p *ir.Program, sp int, vt reflect.Type) { case reflect.Int64: p.Add(ir.OP_i64) case reflect.Uint: - p.Add(ir.OP_uint()) + p.Add(ir.OP_uint(), ir.OP_ui) case reflect.Uint8: p.Add(ir.OP_u8) case reflect.Uint16: @@ -301,7 +301,7 @@ func (self *Compiler) compileMapBodyTextKey(p *ir.Program, vk reflect.Type) { case reflect.Bool: p.Key(ir.OP_bool) case reflect.Int: - p.Key(ir.OP_int()) + p.Key(ir.OP_int(), ir.OP_i) case reflect.Int8: p.Key(ir.OP_i8) case reflect.Int16: @@ -311,7 +311,7 @@ func (self *Compiler) compileMapBodyTextKey(p *ir.Program, vk reflect.Type) { case reflect.Int64: p.Key(ir.OP_i64) case reflect.Uint: - p.Key(ir.OP_uint()) + p.Key(ir.OP_uint(), ir.OP_ui) case reflect.Uint8: p.Key(ir.OP_u8) case reflect.Uint16: diff --git a/internal/encoder/encoder.go b/internal/encoder/encoder.go index 4cba1a168..72862215b 100644 --- a/internal/encoder/encoder.go +++ b/internal/encoder/encoder.go @@ -73,6 +73,9 @@ const ( // Encode Infinity or Nan float into `null`, instead of returning an error. EncodeNullForInfOrNan Options = 1 << alg.BitEncodeNullForInfOrNan + + // Uint64 into strings on Marshal + Uint64ToString Options = 1 << alg.BitUint64ToString ) // Encoder represents a specific set of encoder configurations. diff --git a/internal/encoder/ir/op.go b/internal/encoder/ir/op.go index a0c693f00..3762d20da 100644 --- a/internal/encoder/ir/op.go +++ b/internal/encoder/ir/op.go @@ -38,10 +38,12 @@ const ( OP_i16 OP_i32 OP_i64 + OP_i OP_u8 OP_u16 OP_u32 OP_u64 + OP_ui OP_f32 OP_f64 OP_str @@ -99,10 +101,12 @@ var OpNames = [256]string{ OP_i16: "i16", OP_i32: "i32", OP_i64: "i64", + OP_i: "i", OP_u8: "u8", OP_u16: "u16", OP_u32: "u32", OP_u64: "u64", + OP_ui: "ui", OP_f32: "f32", OP_f64: "f64", OP_str: "str", @@ -197,12 +201,26 @@ func OP_is_zero_ints() Op { type Instr struct { o Op + co Op + mapKey bool u int // union {op: 8, _: 8, vi: 48}, vi maybe int or len(str) p unsafe.Pointer // maybe GoString.Ptr, or *GoType } -func NewInsOp(op Op) Instr { - return Instr{o: op} +func NewInsOp(op Op, compatOp ...Op) Instr { + i := Instr{o: op, co: op} + if len(compatOp) == 1 { + i.co = compatOp[0] + } + return i +} + +func NewInsKeyOp(op Op, compatOp ...Op) Instr { + i := Instr{o: op, co: op, mapKey: true} + if len(compatOp) == 1 { + i.co = compatOp[0] + } + return i } func NewInsVi(op Op, vi int) Instr { @@ -255,6 +273,14 @@ func (self Instr) Op() Op { return Op(self.o) } +func (self Instr) CompatOp() Op { + return Op(self.co) +} + +func (self Instr) IsMapKey() bool { + return self.mapKey +} + func (self Instr) Vi() int { return self.u } @@ -410,14 +436,14 @@ func (self Program) Rel(v []int) { } } -func (self *Program) Add(op Op) { - *self = append(*self, NewInsOp(op)) +func (self *Program) Add(op Op, co ...Op) { + *self = append(*self, NewInsOp(op, co...)) } -func (self *Program) Key(op Op) { +func (self *Program) Key(op Op, co ...Op) { *self = append(*self, NewInsVi(OP_byte, '"'), - NewInsOp(op), + NewInsKeyOp(op, co...), NewInsVi(OP_byte, '"'), ) } diff --git a/internal/encoder/vm/vm.go b/internal/encoder/vm/vm.go index b75ba807a..ea1ca767a 100644 --- a/internal/encoder/vm/vm.go +++ b/internal/encoder/vm/vm.go @@ -152,8 +152,18 @@ func Execute(b *[]byte, p unsafe.Pointer, s *vars.Stack, flags uint64, prog *ir. v := *(*uint32)(p) buf = alg.U64toa(buf, uint64(v)) case ir.OP_u64: + quote := false + if ins.CompatOp() == ir.OP_u64 && + !ins.IsMapKey() && + flags&(1<