Skip to content

Commit

Permalink
opt: encoder support Uint64ToString
Browse files Browse the repository at this point in the history
  • Loading branch information
period331 committed Dec 16, 2024
1 parent 918f5c6 commit b7038ed
Show file tree
Hide file tree
Showing 10 changed files with 190 additions and 12 deletions.
3 changes: 3 additions & 0 deletions api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
116 changes: 116 additions & 0 deletions encode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Check failure on line 1234 in encode_test.go

View workflow job for this annotation

GitHub Actions / build (1.17.x, X64)

undefined: any

Check failure on line 1234 in encode_test.go

View workflow job for this annotation

GitHub Actions / build (1.18.x, X64)

undeclared name: any (requires version go1.18 or later)

Check failure on line 1234 in encode_test.go

View workflow job for this annotation

GitHub Actions / build (1.17.x)

undefined: any
exceptTrue string
exceptFalse string
}{
{
name: "normal_map",
val: map[string]any{

Check failure on line 1240 in encode_test.go

View workflow job for this annotation

GitHub Actions / build (1.17.x, X64)

undefined: any

Check failure on line 1240 in encode_test.go

View workflow job for this annotation

GitHub Actions / build (1.18.x, X64)

undeclared name: any (requires version go1.18 or later)

Check failure on line 1240 in encode_test.go

View workflow job for this annotation

GitHub Actions / build (1.17.x)

undefined: 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{

Check failure on line 1250 in encode_test.go

View workflow job for this annotation

GitHub Actions / build (1.17.x, X64)

undefined: any

Check failure on line 1250 in encode_test.go

View workflow job for this annotation

GitHub Actions / build (1.18.x, X64)

undeclared name: any (requires version go1.18 or later)

Check failure on line 1250 in encode_test.go

View workflow job for this annotation

GitHub Actions / build (1.17.x)

undefined: 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{

Check failure on line 1260 in encode_test.go

View workflow job for this annotation

GitHub Actions / build (1.17.x, X64)

undefined: any

Check failure on line 1260 in encode_test.go

View workflow job for this annotation

GitHub Actions / build (1.18.x, X64)

undeclared name: any (requires version go1.18 or later)

Check failure on line 1260 in encode_test.go

View workflow job for this annotation

GitHub Actions / build (1.17.x)

undefined: 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{

Check failure on line 1284 in encode_test.go

View workflow job for this annotation

GitHub Actions / build (1.18.x, X64)

undeclared name: any (requires version go1.18 or later)
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

Check failure on line 1305 in encode_test.go

View workflow job for this annotation

GitHub Actions / build (1.18.x, X64)

undeclared name: any (requires version go1.18 or later)
}{map[string]any{"val": struct {

Check failure on line 1306 in encode_test.go

View workflow job for this annotation

GitHub Actions / build (1.18.x, X64)

undeclared name: any (requires version go1.18 or later)
Int64Ptr any

Check failure on line 1307 in encode_test.go

View workflow job for this annotation

GitHub Actions / build (1.18.x, X64)

undeclared name: any (requires version go1.18 or later)
Uint64Ptr any

Check failure on line 1308 in encode_test.go

View workflow job for this annotation

GitHub Actions / build (1.18.x, X64)

undeclared name: any (requires version go1.18 or later)
Int64 any

Check failure on line 1309 in encode_test.go

View workflow job for this annotation

GitHub Actions / build (1.18.x, X64)

undeclared name: any (requires version go1.18 or later)
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)
})
}
}
3 changes: 3 additions & 0 deletions encoder/encoder_native.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
)


Expand Down
3 changes: 2 additions & 1 deletion internal/encoder/alg/opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ const (
BitValidateString
BitNoValidateJSONMarshaler
BitNoEncoderNewline
BitEncodeNullForInfOrNan
BitEncodeNullForInfOrNan
BitUint64ToString

BitPointerValue = 63
)
8 changes: 4 additions & 4 deletions internal/encoder/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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:
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand Down
3 changes: 3 additions & 0 deletions internal/encoder/encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
38 changes: 32 additions & 6 deletions internal/encoder/ir/op.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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, '"'),
)
}
Expand Down
10 changes: 10 additions & 0 deletions internal/encoder/vm/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -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<<alg.BitUint64ToString) != 0 {
buf = append(buf, '"')
quote = true
}
v := *(*uint64)(p)
buf = alg.U64toa(buf, uint64(v))
if quote {
buf = append(buf, '"')
}
case ir.OP_f32:
v := *(*float32)(p)
if math.IsNaN(float64(v)) || math.IsInf(float64(v), 0) {
Expand Down
15 changes: 14 additions & 1 deletion internal/encoder/x86/assembler_regabi_amd64.go
Original file line number Diff line number Diff line change
Expand Up @@ -860,8 +860,21 @@ func (self *Assembler) _asm_OP_u32(_ *ir.Instr) {
self.store_int(16, _F_u64toa, "MOVLQZX")
}

func (self *Assembler) _asm_OP_u64(_ *ir.Instr) {
func (self *Assembler) _asm_OP_u64(i *ir.Instr) {
if i.CompatOp() == ir.OP_i || i.IsMapKey() {
self.store_int(20, _F_u64toa, "MOVQ")
return
}
self.Emit("BTQ", jit.Imm(int64(alg.BitUint64ToString)), _ARG_fv)
self.Sjmp("JC", "_ui64_to_string{n}")
self.store_int(20, _F_u64toa, "MOVQ")
self.Sjmp("JMP", "_ui64_to_string_end{n}")
self.Link("_ui64_to_string{n}")

self.add_char('"')
self.store_int(20, _F_u64toa, "MOVQ")
self.add_char('"')
self.Link("_ui64_to_string_end{n}")
}

func (self *Assembler) _asm_OP_f32(_ *ir.Instr) {
Expand Down
3 changes: 3 additions & 0 deletions sonic.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ func (cfg Config) Froze() API {
if cfg.EncodeNullForInfOrNan {
api.encoderOpts |= encoder.EncodeNullForInfOrNan
}
if cfg.Uint64ToString {
api.encoderOpts |= encoder.Uint64ToString
}

// configure decoder options:
if cfg.NoValidateJSONSkip {
Expand Down

0 comments on commit b7038ed

Please sign in to comment.