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

feat:(decoder) add option CopyString() (#192)

* feat: add option `CopyString()`

* test: diff copy and nocopy decoding bench

* doc: update README.md

* opt: use global link for `escape_string()`

* feat: copy JSON on `Unmarshal()`

* test: add norace test

* fix: nocopy field key

* test: add generic test

* fix: remove useless instruction
This commit is contained in:
Yi Duan 2022-02-28 18:46:25 +08:00 committed by GitHub
parent 8caa4eef05
commit b66168fa77
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 635 additions and 165 deletions

View file

@ -41,22 +41,26 @@ BenchmarkEncoder_Parallel_Binding_JsonIter-16 6169 ns/op 2112.8
BenchmarkEncoder_Parallel_Binding_GoJson-16 3492 ns/op 3733.14 MB/s 9492 B/op 1 allocs/op
BenchmarkEncoder_Parallel_Binding_StdLib-16 5170 ns/op 2521.50 MB/s 9482 B/op 1 allocs/op
BenchmarkDecoder_Generic_Sonic-16 69966 ns/op 186.30 MB/s 49543 B/op 313 allocs/op
BenchmarkDecoder_Generic_StdLib-16 146902 ns/op 88.73 MB/s 50872 B/op 772 allocs/op
BenchmarkDecoder_Generic_JsonIter-16 101592 ns/op 128.31 MB/s 55783 B/op 1068 allocs/op
BenchmarkDecoder_Generic_GoJson-16 97865 ns/op 133.19 MB/s 66367 B/op 973 allocs/op
BenchmarkDecoder_Binding_Sonic-16 31418 ns/op 414.90 MB/s 24779 B/op 34 allocs/op
BenchmarkDecoder_Binding_StdLib-16 131260 ns/op 99.31 MB/s 10576 B/op 208 allocs/op
BenchmarkDecoder_Binding_JsonIter-16 39298 ns/op 331.70 MB/s 14673 B/op 385 allocs/op
BenchmarkDecoder_Binding_GoJson-16 31852 ns/op 409.24 MB/s 22040 B/op 49 allocs/op
BenchmarkDecoder_Parallel_Generic_Sonic-16 10525 ns/op 1238.44 MB/s 49470 B/op 313 allocs/op
BenchmarkDecoder_Parallel_Generic_StdLib-16 60223 ns/op 216.44 MB/s 50875 B/op 772 allocs/op
BenchmarkDecoder_Parallel_Generic_JsonIter-16 60918 ns/op 213.97 MB/s 55817 B/op 1068 allocs/op
BenchmarkDecoder_Parallel_Generic_GoJson-16 48386 ns/op 269.39 MB/s 66425 B/op 974 allocs/op
BenchmarkDecoder_Parallel_Binding_Sonic-16 7319 ns/op 1781.00 MB/s 24889 B/op 34 allocs/op
BenchmarkDecoder_Parallel_Binding_StdLib-16 40494 ns/op 321.90 MB/s 10575 B/op 208 allocs/op
BenchmarkDecoder_Parallel_Binding_JsonIter-16 18840 ns/op 691.89 MB/s 14679 B/op 385 allocs/op
BenchmarkDecoder_Parallel_Binding_GoJson-16 17078 ns/op 763.28 MB/s 22211 B/op 49 allocs/op
BenchmarkDecoder_Generic_Sonic-16 71589 ns/op 182.08 MB/s 57531 B/op 723 allocs/op
BenchmarkDecoder_Generic_Sonic_Fast-16 57653 ns/op 226.10 MB/s 49743 B/op 313 allocs/op
BenchmarkDecoder_Generic_StdLib-16 143584 ns/op 90.78 MB/s 50870 B/op 772 allocs/op
BenchmarkDecoder_Generic_JsonIter-16 94775 ns/op 137.54 MB/s 55783 B/op 1068 allocs/op
BenchmarkDecoder_Generic_GoJson-16 88647 ns/op 147.04 MB/s 66371 B/op 973 allocs/op
BenchmarkDecoder_Binding_Sonic-16 32399 ns/op 402.33 MB/s 27814 B/op 137 allocs/op
BenchmarkDecoder_Binding_Sonic_Fast-16 28655 ns/op 454.89 MB/s 25127 B/op 34 allocs/op
BenchmarkDecoder_Binding_StdLib-16 116617 ns/op 111.78 MB/s 7344 B/op 103 allocs/op
BenchmarkDecoder_Binding_JsonIter-16 36206 ns/op 360.02 MB/s 14673 B/op 385 allocs/op
BenchmarkDecoder_Binding_GoJson-16 29396 ns/op 443.43 MB/s 22042 B/op 49 allocs/op
BenchmarkDecoder_Parallel_Generic_Sonic-16 12243 ns/op 1064.68 MB/s 57135 B/op 723 allocs/op
BenchmarkDecoder_Parallel_Generic_Sonic_Fast-16 10101 ns/op 1290.48 MB/s 49440 B/op 313 allocs/op
BenchmarkDecoder_Parallel_Generic_StdLib-16 57352 ns/op 227.28 MB/s 50877 B/op 772 allocs/op
BenchmarkDecoder_Parallel_Generic_JsonIter-16 58693 ns/op 222.09 MB/s 55814 B/op 1068 allocs/op
BenchmarkDecoder_Parallel_Generic_GoJson-16 45245 ns/op 288.10 MB/s 66430 B/op 974 allocs/op
BenchmarkDecoder_Parallel_Binding_Sonic-16 7035 ns/op 1852.89 MB/s 27731 B/op 137 allocs/op
BenchmarkDecoder_Parallel_Binding_Sonic_Fast-16 6510 ns/op 2002.33 MB/s 24841 B/op 34 allocs/op
BenchmarkDecoder_Parallel_Binding_StdLib-16 33086 ns/op 393.97 MB/s 7344 B/op 103 allocs/op
BenchmarkDecoder_Parallel_Binding_JsonIter-16 17827 ns/op 731.18 MB/s 14680 B/op 385 allocs/op
BenchmarkDecoder_Parallel_Binding_GoJson-16 16813 ns/op 775.29 MB/s 22268 B/op 49 allocs/op
BenchmarkGetOne_Sonic-16 11328 ns/op 1149.64 MB/s 29 B/op 1 allocs/op
BenchmarkGetOne_Gjson-16 12970 ns/op 1004.07 MB/s 0 B/op 0 allocs/op
@ -247,7 +251,12 @@ import (
// If the type is too deep nesting (nesting depth > 5),
// you can set compile recursive depth in Pretouch for better stability in JIT.
err := sonic.Pretouch(reflect.TypeOf(v), option.WithCompileRecursiveDepth(depth))
}
```
### Copy string
When decoding **string values without any escaped characters**, sonic refers them from origin JSON buffer instead of mallocing a new buffer to copy. This helps a lot for CPU performance, but may leave the whole JSON buffer in memory as long as the decoded objects are being used. In practice, we found the extra memory introduced by referring JSON buffer is usually 20% ~ 80% of decoded objects. Once a application holds these objects for a long time (for example, cache the decoded objects for reusing), its inuse memory on server may go up. We provide option `decoder.CopyString()` for users to choose not to refer the JSON buffer, which may cause the decline of CPU performance in some degree.
### Accelerate `encoding.TextMarshaler`
To ensure data security, sonic.Encoder quotes and escapes string values from `encoding.TextMarshaler` interfaces by default, which may degrade performance much if most of your data is in form of them. We provide `encoder.NoQuoteTextMarshaler` to skip these operations, which means you **MUST** ensure their output string escaped and quoted in accordance with [RFC8259](https://datatracker.ietf.org/doc/html/rfc8259).

View file

@ -7,7 +7,7 @@ cd $pwd/encoder
go test -benchmem -run=^$ -benchtime=100000x -bench "^(BenchmarkEncoder_Generic_Sonic|BenchmarkEncoder_Generic_Sonic_Fast|BenchmarkEncoder_Generic_JsonIter|BenchmarkEncoder_Generic_GoJson|BenchmarkEncoder_Generic_StdLib|BenchmarkEncoder_Binding_Sonic|BenchmarkEncoder_Binding_Sonic_Fast|BenchmarkEncoder_Binding_JsonIter|BenchmarkEncoder_Binding_GoJson|BenchmarkEncoder_Binding_StdLib|BenchmarkEncoder_Parallel_Generic_Sonic|BenchmarkEncoder_Parallel_Generic_Sonic_Fast|BenchmarkEncoder_Parallel_Generic_JsonIter|BenchmarkEncoder_Parallel_Generic_GoJson|BenchmarkEncoder_Parallel_Generic_StdLib|BenchmarkEncoder_Parallel_Binding_Sonic|BenchmarkEncoder_Parallel_Binding_Sonic_Fast|BenchmarkEncoder_Parallel_Binding_JsonIter|BenchmarkEncoder_Parallel_Binding_GoJson|BenchmarkEncoder_Parallel_Binding_StdLib)$"
cd $pwd/decoder
go test -benchmem -run=^$ -benchtime=100000x -bench "^(BenchmarkDecoder_Generic_Sonic|BenchmarkDecoder_Generic_StdLib|BenchmarkDecoder_Generic_JsonIter|BenchmarkDecoder_Generic_GoJson|BenchmarkDecoder_Binding_Sonic|BenchmarkDecoder_Binding_StdLib|BenchmarkDecoder_Binding_JsonIter|BenchmarkDecoder_Binding_GoJson|BenchmarkDecoder_Parallel_Generic_Sonic|BenchmarkDecoder_Parallel_Generic_StdLib|BenchmarkDecoder_Parallel_Generic_JsonIter|BenchmarkDecoder_Parallel_Generic_GoJson|BenchmarkDecoder_Parallel_Binding_Sonic|BenchmarkDecoder_Parallel_Binding_StdLib|BenchmarkDecoder_Parallel_Binding_JsonIter|BenchmarkDecoder_Parallel_Binding_GoJson)$"
go test -benchmem -run=^$ -benchtime=100000x -bench "^(BenchmarkDecoder_Generic_Sonic|BenchmarkDecoder_Generic_Sonic_Fast|BenchmarkDecoder_Generic_StdLib|BenchmarkDecoder_Generic_JsonIter|BenchmarkDecoder_Generic_GoJson|BenchmarkDecoder_Binding_Sonic|BenchmarkDecoder_Binding_Sonic_Fast|BenchmarkDecoder_Binding_StdLib|BenchmarkDecoder_Binding_JsonIter|BenchmarkDecoder_Binding_GoJson|BenchmarkDecoder_Parallel_Generic_Sonic|BenchmarkDecoder_Parallel_Generic_Sonic_Fast|BenchmarkDecoder_Parallel_Generic_StdLib|BenchmarkDecoder_Parallel_Generic_JsonIter|BenchmarkDecoder_Parallel_Generic_GoJson|BenchmarkDecoder_Parallel_Binding_Sonic|BenchmarkDecoder_Parallel_Binding_Sonic_Fast|BenchmarkDecoder_Parallel_Binding_StdLib|BenchmarkDecoder_Parallel_Binding_JsonIter|BenchmarkDecoder_Parallel_Binding_GoJson)$"
cd $pwd/ast
go test -benchmem -run=^$ -benchtime=100000x -bench "^(BenchmarkGetOne_Sonic|BenchmarkGetOne_Gjson|BenchmarkGetOne_Jsoniter|BenchmarkGetOne_Parallel_Sonic|BenchmarkGetOne_Parallel_Gjson|BenchmarkGetOne_Parallel_Jsoniter|BenchmarkSetOne_Sonic|BenchmarkSetOne_Sjson|BenchmarkSetOne_Jsoniter|BenchmarkSetOne_Parallel_Sonic|BenchmarkSetOne_Parallel_Sjson|BenchmarkSetOne_Parallel_Jsoniter)$"

View file

@ -71,7 +71,7 @@ const (
_FP_args = 96 // 96 bytes to pass arguments and return values for this function
_FP_fargs = 80 // 80 bytes for passing arguments to other Go functions
_FP_saves = 40 // 40 bytes for saving the registers before CALL instructions
_FP_locals = 88 // 88 bytes for local variables
_FP_locals = 112 // 112 bytes for local variables
)
const (
@ -189,6 +189,12 @@ var (
_VAR_ss_R9 = jit.Ptr(_SP, _FP_fargs + _FP_saves + 80)
)
var (
_VAR_bs_p = jit.Ptr(_SP, _FP_fargs + _FP_saves + 88)
_VAR_bs_n = jit.Ptr(_SP, _FP_fargs + _FP_saves + 96)
_VAR_bs_LR = jit.Ptr(_SP, _FP_fargs + _FP_saves + 104)
)
type _Assembler struct {
jit.BaseAssembler
p _Program
@ -214,6 +220,9 @@ func (self *_Assembler) compile() {
self.prologue()
self.instrs()
self.epilogue()
self.copy_string()
self.escape_string()
self.escape_string_twice()
self.type_error()
self.field_error()
self.range_error()
@ -601,6 +610,79 @@ func (self *_Assembler) parse_unsigned() {
self.check_err()
}
// Pointer: DI, Size: SI, Return: R9
func (self *_Assembler) copy_string() {
self.Link("_copy_string")
self.Emit("MOVQ", _DI, _VAR_bs_p)
self.Emit("MOVQ", _SI, _VAR_bs_n)
self.Emit("MOVQ", _R9, _VAR_bs_LR)
self.malloc(_SI, _AX)
self.Emit("MOVQ", _AX, _VAR_sv_p)
self.Emit("MOVQ", _AX, jit.Ptr(_SP, 0))
self.Emit("MOVQ", _VAR_bs_p, _DI)
self.Emit("MOVQ", _DI, jit.Ptr(_SP, 8))
self.Emit("MOVQ", _VAR_bs_n, _SI)
self.Emit("MOVQ", _SI, jit.Ptr(_SP, 16))
self.call_go(_F_memmove)
self.Emit("MOVQ", _VAR_sv_p, _DI)
self.Emit("MOVQ", _VAR_bs_n, _SI)
self.Emit("MOVQ", _VAR_bs_LR, _R9)
self.Rjmp("JMP", _R9)
}
// Pointer: DI, Size: SI, Return: R9
func (self *_Assembler) escape_string() {
self.Link("_escape_string")
self.Emit("MOVQ" , _DI, _VAR_bs_p)
self.Emit("MOVQ" , _SI, _VAR_bs_n)
self.Emit("MOVQ" , _R9, _VAR_bs_LR)
self.malloc(_SI, _DX) // MALLOC SI, DX
self.Emit("MOVQ" , _DX, _VAR_sv_p)
self.Emit("MOVQ" , _VAR_bs_p, _DI)
self.Emit("MOVQ" , _VAR_bs_n, _SI)
self.Emit("LEAQ" , _VAR_sr, _CX) // LEAQ sr, CX
self.Emit("XORL" , _R8, _R8) // XORL R8, R8
self.Emit("BTQ" , jit.Imm(_F_disable_urc), _ARG_fv) // BTQ ${_F_disable_urc}, fv
self.Emit("SETCC", _R8) // SETCC R8
self.Emit("SHLQ" , jit.Imm(types.B_UNICODE_REPLACE), _R8) // SHLQ ${types.B_UNICODE_REPLACE}, R8
self.call(_F_unquote) // CALL unquote
self.Emit("MOVQ" , _VAR_bs_n, _SI) // MOVQ ${n}, SI
self.Emit("ADDQ" , jit.Imm(1), _SI) // ADDQ $1, SI
self.Emit("TESTQ", _AX, _AX) // TESTQ AX, AX
self.Sjmp("JS" , _LB_unquote_error) // JS _unquote_error
self.Emit("MOVQ" , _AX, _SI)
self.Emit("MOVQ" , _VAR_sv_p, _DI)
self.Emit("MOVQ" , _VAR_bs_LR, _R9)
self.Rjmp("JMP", _R9)
}
func (self *_Assembler) escape_string_twice() {
self.Link("_escape_string_twice")
self.Emit("MOVQ" , _DI, _VAR_bs_p)
self.Emit("MOVQ" , _SI, _VAR_bs_n)
self.Emit("MOVQ" , _R9, _VAR_bs_LR)
self.malloc(_SI, _DX) // MALLOC SI, DX
self.Emit("MOVQ" , _DX, _VAR_sv_p)
self.Emit("MOVQ" , _VAR_bs_p, _DI)
self.Emit("MOVQ" , _VAR_bs_n, _SI)
self.Emit("LEAQ" , _VAR_sr, _CX) // LEAQ sr, CX
self.Emit("MOVL" , jit.Imm(types.F_DOUBLE_UNQUOTE), _R8) // MOVL ${types.F_DOUBLE_UNQUOTE}, R8
self.Emit("BTQ" , jit.Imm(_F_disable_urc), _ARG_fv) // BTQ ${_F_disable_urc}, AX
self.Emit("XORL" , _AX, _AX) // XORL AX, AX
self.Emit("SETCC", _AX) // SETCC AX
self.Emit("SHLQ" , jit.Imm(types.B_UNICODE_REPLACE), _AX) // SHLQ ${types.B_UNICODE_REPLACE}, AX
self.Emit("ORQ" , _AX, _R8) // ORQ AX, R8
self.call(_F_unquote) // CALL unquote
self.Emit("MOVQ" , _VAR_bs_n, _SI) // MOVQ ${n}, SI
self.Emit("ADDQ" , jit.Imm(3), _SI) // ADDQ $3, SI
self.Emit("TESTQ", _AX, _AX) // TESTQ AX, AX
self.Sjmp("JS" , _LB_unquote_error) // JS _unquote_error
self.Emit("MOVQ" , _AX, _SI)
self.Emit("MOVQ" , _VAR_sv_p, _DI)
self.Emit("MOVQ" , _VAR_bs_LR, _R9)
self.Rjmp("JMP", _R9)
}
/** Range Checking Routines **/
var (
@ -669,37 +751,28 @@ func (self *_Assembler) slice_from_r(p obj.Addr, d int64) {
self.Emit("LEAQ", jit.Sib(_IC, p, 1, d), _SI) // LEAQ d(IC)(${p}), SI
}
func (self *_Assembler) unquote_once(p obj.Addr, n obj.Addr, stack bool) {
func (self *_Assembler) unquote_once(p obj.Addr, n obj.Addr, stack bool, copy bool) {
self.slice_from(_VAR_st_Iv, -1) // SLICE st.Iv, $-1
self.Emit("CMPQ" , _VAR_st_Ep, jit.Imm(-1)) // CMPQ st.Ep, $-1
self.Sjmp("JE" , "_noescape_{n}") // JE _noescape_{n}
self.Byte(0x4c, 0x8d, 0x0d) // LEAQ (PC), R9
self.Sref("_unquote_once_write_{n}", 4)
self.Sjmp("JMP" , "_escape_string")
self.Link("_noescape_{n}") // _noescape_{n}:
if copy {
self.Emit("BTQ" , jit.Imm(_F_copy_string), _ARG_fv)
self.Sjmp("JNC", "_unquote_once_write_{n}")
self.Byte(0x4c, 0x8d, 0x0d) // LEAQ (PC), R9
self.Sref("_unquote_once_write_{n}", 4)
self.Sjmp("JMP", "_copy_string")
}
self.Link("_unquote_once_write_{n}")
self.Emit("MOVQ" , _SI, n) // MOVQ SI, ${n}
if stack {
self.Emit("MOVQ", _DI, p) // MOVQ DI, ${p}
self.Emit("MOVQ", _DI, p)
} else {
self.WriteRecNotAX(10, _DI, p, false, false)
}
self.Emit("CMPQ" , _VAR_st_Ep, jit.Imm(-1)) // CMPQ st.Ep, $-1
self.Sjmp("JE" , "_noescape_{n}") // JE _noescape_{n}
self.malloc(_SI, _DX) // MALLOC SI, DX
self.Emit("MOVQ" , p, _DI) // MOVQ ${p}, DI
self.Emit("MOVQ" , n, _SI) // MOVQ ${n}, SI
if stack {
// no need for writeBarrier
self.Emit("MOVQ", _DX, p) // MOVQ DX, ${p}
} else {
self.WriteRecNotAX(2, _DX, p, true, false) // MOVQ DX, ${p}
}
self.Emit("LEAQ" , _VAR_sr, _CX) // LEAQ sr, CX
self.Emit("XORL" , _R8, _R8) // XORL R8, R8
self.Emit("BTQ" , jit.Imm(_F_disable_urc), _ARG_fv) // BTQ ${_F_disable_urc}, fv
self.Emit("SETCC", _R8) // SETCC R8
self.Emit("SHLQ" , jit.Imm(types.B_UNICODE_REPLACE), _R8) // SHLQ ${types.B_UNICODE_REPLACE}, R8
self.call(_F_unquote) // CALL unquote
self.Emit("MOVQ" , n, _SI) // MOVQ ${n}, SI
self.Emit("ADDQ" , jit.Imm(1), _SI) // ADDQ $1, SI
self.Emit("TESTQ", _AX, _AX) // TESTQ AX, AX
self.Sjmp("JS" , _LB_unquote_error) // JS _unquote_error
self.Emit("MOVQ" , _AX, n) // MOVQ AX, ${n}
self.Link("_noescape_{n}") // _noescape_{n}:
}
func (self *_Assembler) unquote_twice(p obj.Addr, n obj.Addr, stack bool) {
@ -710,39 +783,26 @@ func (self *_Assembler) unquote_twice(p obj.Addr, n obj.Addr, stack bool) {
self.Emit("CMPB" , jit.Sib(_IP, _IC, 1, -2), jit.Imm('"')) // CMPB -2(IP)(IC), $'"'
self.Sjmp("JNE" , _LB_char_m2_error) // JNE _char_m2_error
self.slice_from(_VAR_st_Iv, -3) // SLICE st.Iv, $-3
self.Emit("MOVQ" , _SI, n) // MOVQ SI, ${n}
self.Emit("MOVQ" , _SI, _AX) // MOVQ SI, AX
if stack {
self.Emit("MOVQ" , _DI, p) // MOVQ DI, ${p}
} else {
self.WriteRecNotAX(9, _DI, p, false, false)
}
self.Emit("ADDQ" , _VAR_st_Iv, _AX) // ADDQ st.Iv, AX
self.Emit("CMPQ" , _VAR_st_Ep, _AX) // CMPQ st.Ep, AX
self.Sjmp("JE" , "_noescape_{n}") // JE _noescape_{n}
self.malloc(_SI, _DX) // MALLOC SI, DX
self.Emit("MOVQ" , p, _DI) // MOVQ ${p}, DI
self.Emit("MOVQ" , n, _SI) // MOVQ ${n}, SI
if stack {
// no need for writeBarrier
self.Emit("MOVQ", _DX, p) // MOVQ DX, ${p}
} else {
self.WriteRecNotAX(2, _DX, p, true, false) // MOVQ DX, ${p}
}
self.Emit("LEAQ" , _VAR_sr, _CX) // LEAQ sr, CX
self.Emit("MOVL" , jit.Imm(types.F_DOUBLE_UNQUOTE), _R8) // MOVL ${types.F_DOUBLE_UNQUOTE}, R8
self.Emit("BTQ" , jit.Imm(_F_disable_urc), _ARG_fv) // BTQ ${_F_disable_urc}, AX
self.Emit("XORL" , _AX, _AX) // XORL AX, AX
self.Emit("SETCC", _AX) // SETCC AX
self.Emit("SHLQ" , jit.Imm(types.B_UNICODE_REPLACE), _AX) // SHLQ ${types.B_UNICODE_REPLACE}, AX
self.Emit("ORQ" , _AX, _R8) // ORQ AX, R8
self.call(_F_unquote) // CALL unquote
self.Emit("MOVQ" , n, _SI) // MOVQ ${n}, SI
self.Emit("ADDQ" , jit.Imm(3), _SI) // ADDQ $3, SI
self.Emit("TESTQ", _AX, _AX) // TESTQ AX, AX
self.Sjmp("JS" , _LB_unquote_error) // JS _unquote_error
self.Emit("MOVQ" , _AX, n) // MOVQ AX, ${n}
self.Byte(0x4c, 0x8d, 0x0d) // LEAQ (PC), R9
self.Sref("_unquote_twice_write_{n}", 4)
self.Sjmp("JMP" , "_escape_string_twice")
self.Link("_noescape_{n}") // _noescape_{n}:
self.Emit("BTQ" , jit.Imm(_F_copy_string), _ARG_fv)
self.Sjmp("JNC", "_unquote_twice_write_{n}")
self.Byte(0x4c, 0x8d, 0x0d) // LEAQ (PC), R9
self.Sref("_unquote_twice_write_{n}", 4)
self.Sjmp("JMP", "_copy_string")
self.Link("_unquote_twice_write_{n}")
self.Emit("MOVQ" , _SI, n) // MOVQ SI, ${n}
if stack {
self.Emit("MOVQ", _DI, p)
} else {
self.WriteRecNotAX(12, _DI, p, false, false)
}
}
/** Memory Clearing Routines **/
@ -885,7 +945,7 @@ func (self *_Assembler) unmarshal_json(t reflect.Type, deref bool) {
func (self *_Assembler) unmarshal_text(t reflect.Type, deref bool) {
self.parse_string() // PARSE STRING
self.unquote_once(_VAR_sv_p, _VAR_sv_n, true) // UNQUOTE once, sv.p, sv.n
self.unquote_once(_VAR_sv_p, _VAR_sv_n, true, true) // UNQUOTE once, sv.p, sv.n
self.unmarshal_func(t, _F_decodeTextUnmarshaler, deref) // UNMARSHAL text, ${t}, ${deref}
}
@ -956,6 +1016,7 @@ func (self *_Assembler) decode_dynamic(vt obj.Addr, vp obj.Addr) {
var (
_F_memequal = jit.Func(memequal)
_F_memmove = jit.Func(memmove)
_F_growslice = jit.Func(growslice)
_F_makeslice = jit.Func(makeslice)
_F_makemap_small = jit.Func(makemap_small)
@ -1040,7 +1101,7 @@ func (self *_Assembler) _asm_OP_dyn(p *_Instr) {
func (self *_Assembler) _asm_OP_str(_ *_Instr) {
self.parse_string() // PARSE STRING
self.unquote_once(jit.Ptr(_VP, 0), jit.Ptr(_VP, 8), false) // UNQUOTE once, (VP), 8(VP)
self.unquote_once(jit.Ptr(_VP, 0), jit.Ptr(_VP, 8), false, true) // UNQUOTE once, (VP), 8(VP)
}
func (self *_Assembler) _asm_OP_bin(_ *_Instr) {
@ -1104,8 +1165,14 @@ func (self *_Assembler) _asm_OP_bool(_ *_Instr) {
func (self *_Assembler) _asm_OP_num(_ *_Instr) {
self.parse_number() // PARSE NUMBER
self.slice_from(_VAR_st_Ep, 0) // SLICE st.Ep, $0
self.WriteRecNotAX(5, _DI, jit.Ptr(_VP, 0), false, false) // MOVQ DI, (VP)
self.Emit("BTQ", jit.Imm(_F_copy_string), _ARG_fv)
self.Sjmp("JNC", "_num_write_{n}")
self.Byte(0x4c, 0x8d, 0x0d) // LEAQ (PC), R9
self.Sref("_num_write_{n}", 4)
self.Sjmp("JMP", "_copy_string")
self.Link("_num_write_{n}")
self.Emit("MOVQ", _SI, jit.Ptr(_VP, 8)) // MOVQ SI, 8(VP)
self.WriteRecNotAX(13, _DI, jit.Ptr(_VP, 0), false, false)
}
func (self *_Assembler) _asm_OP_i8(_ *_Instr) {
@ -1316,7 +1383,7 @@ func (self *_Assembler) _asm_OP_map_key_f64(p *_Instr) {
func (self *_Assembler) _asm_OP_map_key_str(p *_Instr) {
self.parse_string() // PARSE STRING
self.unquote_once(_VAR_sv_p, _VAR_sv_n, true) // UNQUOTE once, sv.p, sv.n
self.unquote_once(_VAR_sv_p, _VAR_sv_n, true, true) // UNQUOTE once, sv.p, sv.n
if vt := p.vt(); !mapfast(vt) {
self.valloc(vt.Key(), _DI)
self.Emit("MOVOU", _VAR_sv, _X0)
@ -1331,13 +1398,13 @@ func (self *_Assembler) _asm_OP_map_key_str(p *_Instr) {
func (self *_Assembler) _asm_OP_map_key_utext(p *_Instr) {
self.parse_string() // PARSE STRING
self.unquote_once(_VAR_sv_p, _VAR_sv_n, true) // UNQUOTE once, sv.p, sv.n
self.unquote_once(_VAR_sv_p, _VAR_sv_n, true, true) // UNQUOTE once, sv.p, sv.n
self.mapassign_utext(p.vt(), false) // MAPASSIGN utext, ${p.vt()}, false
}
func (self *_Assembler) _asm_OP_map_key_utext_p(p *_Instr) {
self.parse_string() // PARSE STRING
self.unquote_once(_VAR_sv_p, _VAR_sv_n, true) // UNQUOTE once, sv.p, sv.n
self.unquote_once(_VAR_sv_p, _VAR_sv_n, true, false) // UNQUOTE once, sv.p, sv.n
self.mapassign_utext(p.vt(), true) // MAPASSIGN utext, ${p.vt()}, true
}
@ -1419,7 +1486,7 @@ func (self *_Assembler) _asm_OP_struct_field(p *_Instr) {
self.Emit("MOVQ" , jit.Imm(-1), _AX) // MOVQ $-1, AX
self.Emit("MOVQ" , _AX, _VAR_sr) // MOVQ AX, sr
self.parse_string() // PARSE STRING
self.unquote_once(_VAR_sv_p, _VAR_sv_n, true) // UNQUOTE once, sv.p, sv.n
self.unquote_once(_VAR_sv_p, _VAR_sv_n, true, false) // UNQUOTE once, sv.p, sv.n
self.Emit("LEAQ" , _VAR_sv, _AX) // LEAQ sv, AX
self.Emit("XORL" , _CX, _CX) // XORL CX, CX
self.Emit("MOVQ" , _AX, jit.Ptr(_SP, 0)) // MOVQ AX, (SP)

View file

@ -71,7 +71,7 @@ const (
_FP_args = 72 // 72 bytes to pass and spill register arguements
_FP_fargs = 80 // 80 bytes for passing arguments to other Go functions
_FP_saves = 48 // 48 bytes for saving the registers before CALL instructions
_FP_locals = 88 // 88 bytes for local variables
_FP_locals = 112 // 112 bytes for local variables
)
const (
@ -184,6 +184,12 @@ var (
_VAR_ss_R9 = jit.Ptr(_SP, _FP_fargs + _FP_saves + 80)
)
var (
_VAR_bs_p = jit.Ptr(_SP, _FP_fargs + _FP_saves + 88)
_VAR_bs_n = jit.Ptr(_SP, _FP_fargs + _FP_saves + 96)
_VAR_bs_LR = jit.Ptr(_SP, _FP_fargs + _FP_saves + 104)
)
type _Assembler struct {
jit.BaseAssembler
p _Program
@ -209,6 +215,9 @@ func (self *_Assembler) compile() {
self.prologue()
self.instrs()
self.epilogue()
self.copy_string()
self.escape_string()
self.escape_string_twice()
self.type_error()
self.field_error()
self.range_error()
@ -532,7 +541,7 @@ var (
_F_mallocgc = jit.Func(mallocgc)
)
func (self *_Assembler) malloc(nb obj.Addr, ret obj.Addr) {
func (self *_Assembler) malloc_AX(nb obj.Addr, ret obj.Addr) {
self.Emit("MOVQ", nb, _AX) // MOVQ ${nb}, AX
self.Emit("MOVQ", _T_byte, _BX) // MOVQ ${type(byte)}, BX
self.Emit("XORL", _CX, _CX) // XORL CX, CX
@ -611,6 +620,75 @@ func (self *_Assembler) parse_unsigned() {
self.check_err()
}
// Pointer: DI, Size: SI, Return: R9
func (self *_Assembler) copy_string() {
self.Link("_copy_string")
self.Emit("MOVQ", _DI, _VAR_bs_p)
self.Emit("MOVQ", _SI, _VAR_bs_n)
self.Emit("MOVQ", _R9, _VAR_bs_LR)
self.malloc_AX(_SI, _VAR_sv_p)
self.Emit("MOVQ", _VAR_bs_p, _BX)
self.Emit("MOVQ", _VAR_bs_n, _CX)
self.call_go(_F_memmove)
self.Emit("MOVQ", _VAR_sv_p, _DI)
self.Emit("MOVQ", _VAR_bs_n, _SI)
self.Emit("MOVQ", _VAR_bs_LR, _R9)
self.Rjmp("JMP", _R9)
}
// Pointer: DI, Size: SI, Return: R9
func (self *_Assembler) escape_string() {
self.Link("_escape_string")
self.Emit("MOVQ" , _DI, _VAR_bs_p)
self.Emit("MOVQ" , _SI, _VAR_bs_n)
self.Emit("MOVQ" , _R9, _VAR_bs_LR)
self.malloc_AX(_SI, _DX) // MALLOC SI, DX
self.Emit("MOVQ" , _DX, _VAR_sv_p)
self.Emit("MOVQ" , _VAR_bs_p, _DI)
self.Emit("MOVQ" , _VAR_bs_n, _SI)
self.Emit("LEAQ" , _VAR_sr, _CX) // LEAQ sr, CX
self.Emit("XORL" , _R8, _R8) // XORL R8, R8
self.Emit("BTQ" , jit.Imm(_F_disable_urc), _ARG_fv) // BTQ ${_F_disable_urc}, fv
self.Emit("SETCC", _R8) // SETCC R8
self.Emit("SHLQ" , jit.Imm(types.B_UNICODE_REPLACE), _R8) // SHLQ ${types.B_UNICODE_REPLACE}, R8
self.call_c(_F_unquote) // CALL unquote
self.Emit("MOVQ" , _VAR_bs_n, _SI) // MOVQ ${n}, SI
self.Emit("ADDQ" , jit.Imm(1), _SI) // ADDQ $1, SI
self.Emit("TESTQ", _AX, _AX) // TESTQ AX, AX
self.Sjmp("JS" , _LB_unquote_error) // JS _unquote_error
self.Emit("MOVQ" , _AX, _SI)
self.Emit("MOVQ" , _VAR_sv_p, _DI)
self.Emit("MOVQ" , _VAR_bs_LR, _R9)
self.Rjmp("JMP", _R9)
}
func (self *_Assembler) escape_string_twice() {
self.Link("_escape_string_twice")
self.Emit("MOVQ" , _DI, _VAR_bs_p)
self.Emit("MOVQ" , _SI, _VAR_bs_n)
self.Emit("MOVQ" , _R9, _VAR_bs_LR)
self.malloc_AX(_SI, _DX) // MALLOC SI, DX
self.Emit("MOVQ" , _DX, _VAR_sv_p)
self.Emit("MOVQ" , _VAR_bs_p, _DI)
self.Emit("MOVQ" , _VAR_bs_n, _SI)
self.Emit("LEAQ" , _VAR_sr, _CX) // LEAQ sr, CX
self.Emit("MOVL" , jit.Imm(types.F_DOUBLE_UNQUOTE), _R8) // MOVL ${types.F_DOUBLE_UNQUOTE}, R8
self.Emit("BTQ" , jit.Imm(_F_disable_urc), _ARG_fv) // BTQ ${_F_disable_urc}, AX
self.Emit("XORL" , _AX, _AX) // XORL AX, AX
self.Emit("SETCC", _AX) // SETCC AX
self.Emit("SHLQ" , jit.Imm(types.B_UNICODE_REPLACE), _AX) // SHLQ ${types.B_UNICODE_REPLACE}, AX
self.Emit("ORQ" , _AX, _R8) // ORQ AX, R8
self.call_c(_F_unquote) // CALL unquote
self.Emit("MOVQ" , _VAR_bs_n, _SI) // MOVQ ${n}, SI
self.Emit("ADDQ" , jit.Imm(3), _SI) // ADDQ $3, SI
self.Emit("TESTQ", _AX, _AX) // TESTQ AX, AX
self.Sjmp("JS" , _LB_unquote_error) // JS _unquote_error
self.Emit("MOVQ" , _AX, _SI)
self.Emit("MOVQ" , _VAR_sv_p, _DI)
self.Emit("MOVQ" , _VAR_bs_LR, _R9)
self.Rjmp("JMP", _R9)
}
/** Range Checking Routines **/
var (
@ -679,36 +757,28 @@ func (self *_Assembler) slice_from_r(p obj.Addr, d int64) {
self.Emit("LEAQ", jit.Sib(_IC, p, 1, d), _SI) // LEAQ d(IC)(${p}), SI
}
func (self *_Assembler) unquote_once(p obj.Addr, n obj.Addr, stack bool) {
func (self *_Assembler) unquote_once(p obj.Addr, n obj.Addr, stack bool, copy bool) {
self.slice_from(_VAR_st_Iv, -1) // SLICE st.Iv, $-1
self.Emit("MOVQ" , _SI, n) // MOVQ SI, ${n}
self.Emit("CMPQ", _VAR_st_Ep, jit.Imm(-1)) // CMPQ st.Ep, $-1
self.Sjmp("JE" , "_noescape_{n}") // JE _escape_{n}
self.Byte(0x4c, 0x8d, 0x0d) // LEAQ (PC), R9
self.Sref("_unquote_once_write_{n}", 4)
self.Sjmp("JMP" , "_escape_string")
self.Link("_noescape_{n}")
if copy {
self.Emit("BTQ" , jit.Imm(_F_copy_string), _ARG_fv)
self.Sjmp("JNC", "_unquote_once_write_{n}")
self.Byte(0x4c, 0x8d, 0x0d) // LEAQ (PC), R9
self.Sref("_unquote_once_write_{n}", 4)
self.Sjmp("JMP", "_copy_string")
}
self.Link("_unquote_once_write_{n}")
self.Emit("MOVQ", _SI, n) // MOVQ SI, ${n}
if stack {
self.Emit("MOVQ", _DI, p) // MOVQ DI, ${p}
self.Emit("MOVQ", _DI, p)
} else {
self.WriteRecNotAX(10, _DI, p, false, false)
}
self.Emit("CMPQ" , _VAR_st_Ep, jit.Imm(-1)) // CMPQ st.Ep, $-1
self.Sjmp("JE" , "_noescape_{n}") // JE _noescape_{n}
self.malloc(_SI, _DX) // MALLOC SI, DX
self.Emit("MOVQ" , p, _DI) // MOVQ ${p}, DI
self.Emit("MOVQ" , n, _SI) // MOVQ ${n}, SI
if stack {
self.Emit("MOVQ", _DX, p) // MOVQ DX, ${p}
} else {
self.WriteRecNotAX(2, _DX, p, true, false) // MOVQ DX, ${p}
}
self.Emit("LEAQ" , _VAR_sr, _CX) // LEAQ sr, CX
self.Emit("XORL" , _R8, _R8) // XORL R8, R8
self.Emit("BTQ" , jit.Imm(_F_disable_urc), _ARG_fv) // BTQ ${_F_disable_urc}, fv
self.Emit("SETCC", _R8) // SETCC R8
self.Emit("SHLQ" , jit.Imm(types.B_UNICODE_REPLACE), _R8) // SHLQ ${types.B_UNICODE_REPLACE}, R8
self.call_c(_F_unquote) // CALL unquote
self.Emit("MOVQ" , n, _SI) // MOVQ ${n}, SI
self.Emit("ADDQ" , jit.Imm(1), _SI) // ADDQ $1, SI
self.Emit("TESTQ", _AX, _AX) // TESTQ AX, AX
self.Sjmp("JS" , _LB_unquote_error) // JS _unquote_error
self.Emit("MOVQ" , _AX, n) // MOVQ AX, ${n}
self.Link("_noescape_{n}") // _noescape_{n}:
}
func (self *_Assembler) unquote_twice(p obj.Addr, n obj.Addr, stack bool) {
@ -719,38 +789,27 @@ func (self *_Assembler) unquote_twice(p obj.Addr, n obj.Addr, stack bool) {
self.Emit("CMPB" , jit.Sib(_IP, _IC, 1, -2), jit.Imm('"')) // CMPB -2(IP)(IC), $'"'
self.Sjmp("JNE" , _LB_char_m2_error) // JNE _char_m2_error
self.slice_from(_VAR_st_Iv, -3) // SLICE st.Iv, $-3
self.Emit("MOVQ" , _SI, n) // MOVQ SI, ${n}
self.Emit("MOVQ" , _SI, _AX) // MOVQ SI, AX
if stack {
self.Emit("MOVQ" , _DI, p) // MOVQ DI, ${p}
} else {
self.WriteRecNotAX(9, _DI, p, false, false)
}
self.Emit("ADDQ" , _VAR_st_Iv, _AX) // ADDQ st.Iv, AX
self.Emit("CMPQ" , _VAR_st_Ep, _AX) // CMPQ st.Ep, AX
self.Sjmp("JE" , "_noescape_{n}") // JE _noescape_{n}
self.malloc(_SI, _DX) // MALLOC SI, DX
self.Emit("MOVQ" , p, _DI) // MOVQ ${p}, DI
self.Emit("MOVQ" , n, _SI) // MOVQ ${n}, SI
if stack {
self.Emit("MOVQ", _DX, p) // MOVQ DX, ${p}
} else {
self.WriteRecNotAX(2, _DX, p, true, false) // MOVQ DX, ${p}
}
self.Emit("LEAQ" , _VAR_sr, _CX) // LEAQ sr, CX
self.Emit("MOVL" , jit.Imm(types.F_DOUBLE_UNQUOTE), _R8) // MOVL ${types.F_DOUBLE_UNQUOTE}, R8
self.Emit("BTQ" , jit.Imm(_F_disable_urc), _ARG_fv) // BTQ ${_F_disable_urc}, AX
self.Emit("XORL" , _AX, _AX) // XORL AX, AX
self.Emit("SETCC", _AX) // SETCC AX
self.Emit("SHLQ" , jit.Imm(types.B_UNICODE_REPLACE), _AX) // SHLQ ${types.B_UNICODE_REPLACE}, AX
self.Emit("ORQ" , _AX, _R8) // ORQ AX, R8
self.call_c(_F_unquote) // CALL unquote
self.Emit("MOVQ" , n, _SI) // MOVQ ${n}, SI
self.Emit("ADDQ" , jit.Imm(3), _SI) // ADDQ $3, SI
self.Emit("TESTQ", _AX, _AX) // TESTQ AX, AX
self.Sjmp("JS" , _LB_unquote_error) // JS _unquote_error
self.Emit("MOVQ" , _AX, n) // MOVQ AX, ${n}
self.Byte(0x4c, 0x8d, 0x0d) // LEAQ (PC), R9
self.Sref("_unquote_twice_write_{n}", 4)
self.Sjmp("JMP" , "_escape_string_twice")
self.Link("_noescape_{n}") // _noescape_{n}:
self.Emit("BTQ" , jit.Imm(_F_copy_string), _ARG_fv)
self.Sjmp("JNC", "_unquote_twice_write_{n}")
self.Byte(0x4c, 0x8d, 0x0d) // LEAQ (PC), R9
self.Sref("_unquote_twice_write_{n}", 4)
self.Sjmp("JMP", "_copy_string")
self.Link("_unquote_twice_write_{n}")
self.Emit("MOVQ" , _SI, n) // MOVQ SI, ${n}
if stack {
self.Emit("MOVQ", _DI, p)
} else {
self.WriteRecNotAX(12, _DI, p, false, false)
}
self.Link("_unquote_twice_end_{n}")
}
/** Memory Clearing Routines **/
@ -887,7 +946,7 @@ func (self *_Assembler) unmarshal_json(t reflect.Type, deref bool) {
func (self *_Assembler) unmarshal_text(t reflect.Type, deref bool) {
self.parse_string() // PARSE STRING
self.unquote_once(_VAR_sv_p, _VAR_sv_n, true) // UNQUOTE once, sv.p, sv.n
self.unquote_once(_VAR_sv_p, _VAR_sv_n, true, true) // UNQUOTE once, sv.p, sv.n
self.unmarshal_func(t, _F_decodeTextUnmarshaler, deref) // UNMARSHAL text, ${t}, ${deref}
}
@ -953,6 +1012,7 @@ func (self *_Assembler) decode_dynamic(vt obj.Addr, vp obj.Addr) {
var (
_F_memequal = jit.Func(memequal)
_F_memmove = jit.Func(memmove)
_F_growslice = jit.Func(growslice)
_F_makeslice = jit.Func(makeslice)
_F_makemap_small = jit.Func(makemap_small)
@ -1038,7 +1098,7 @@ func (self *_Assembler) _asm_OP_dyn(p *_Instr) {
func (self *_Assembler) _asm_OP_str(_ *_Instr) {
self.parse_string() // PARSE STRING
self.unquote_once(jit.Ptr(_VP, 0), jit.Ptr(_VP, 8), false) // UNQUOTE once, (VP), 8(VP)
self.unquote_once(jit.Ptr(_VP, 0), jit.Ptr(_VP, 8), false, true) // UNQUOTE once, (VP), 8(VP)
}
func (self *_Assembler) _asm_OP_bin(_ *_Instr) {
@ -1049,7 +1109,7 @@ func (self *_Assembler) _asm_OP_bin(_ *_Instr) {
self.Emit("SHRQ" , jit.Imm(2), _SI) // SHRQ $2, SI
self.Emit("LEAQ" , jit.Sib(_SI, _SI, 2, 0), _SI) // LEAQ (SI)(SI*2), SI
self.Emit("MOVQ" , _SI, jit.Ptr(_VP, 16)) // MOVQ SI, 16(VP)
self.malloc(_SI, _SI) // MALLOC SI, SI
self.malloc_AX(_SI, _SI) // MALLOC SI, SI
/* check for AVX2 support */
if !cpu.HasAVX2 {
@ -1102,8 +1162,14 @@ func (self *_Assembler) _asm_OP_bool(_ *_Instr) {
func (self *_Assembler) _asm_OP_num(_ *_Instr) {
self.parse_number() // PARSE NUMBER
self.slice_from(_VAR_st_Ep, 0) // SLICE st.Ep, $0
self.WriteRecNotAX(5, _DI, jit.Ptr(_VP, 0), false, false) // MOVQ DI, (VP)
self.Emit("BTQ", jit.Imm(_F_copy_string), _ARG_fv)
self.Sjmp("JNC", "_num_write_{n}")
self.Byte(0x4c, 0x8d, 0x0d) // LEAQ (PC), R9
self.Sref("_num_write_{n}", 4)
self.Sjmp("JMP", "_copy_string")
self.Link("_num_write_{n}")
self.Emit("MOVQ", _SI, jit.Ptr(_VP, 8)) // MOVQ SI, 8(VP)
self.WriteRecNotAX(13, _DI, jit.Ptr(_VP, 0), false, false)
}
func (self *_Assembler) _asm_OP_i8(_ *_Instr) {
@ -1315,7 +1381,7 @@ func (self *_Assembler) _asm_OP_map_key_f64(p *_Instr) {
func (self *_Assembler) _asm_OP_map_key_str(p *_Instr) {
self.parse_string() // PARSE STRING
self.unquote_once(_VAR_sv_p, _VAR_sv_n, true) // UNQUOTE once, sv.p, sv.n
self.unquote_once(_VAR_sv_p, _VAR_sv_n, true, true) // UNQUOTE once, sv.p, sv.n
if vt := p.vt(); !mapfast(vt) {
self.valloc(vt.Key(), _DI)
self.Emit("MOVOU", _VAR_sv, _X0)
@ -1328,13 +1394,13 @@ func (self *_Assembler) _asm_OP_map_key_str(p *_Instr) {
func (self *_Assembler) _asm_OP_map_key_utext(p *_Instr) {
self.parse_string() // PARSE STRING
self.unquote_once(_VAR_sv_p, _VAR_sv_n, true) // UNQUOTE once, sv.p, sv.n
self.unquote_once(_VAR_sv_p, _VAR_sv_n, true, true) // UNQUOTE once, sv.p, sv.n
self.mapassign_utext(p.vt(), false) // MAPASSIGN utext, ${p.vt()}, false
}
func (self *_Assembler) _asm_OP_map_key_utext_p(p *_Instr) {
self.parse_string() // PARSE STRING
self.unquote_once(_VAR_sv_p, _VAR_sv_n, true) // UNQUOTE once, sv.p, sv.n
self.unquote_once(_VAR_sv_p, _VAR_sv_n, true, true) // UNQUOTE once, sv.p, sv.n
self.mapassign_utext(p.vt(), true) // MAPASSIGN utext, ${p.vt()}, true
}
@ -1408,7 +1474,7 @@ func (self *_Assembler) _asm_OP_struct_field(p *_Instr) {
self.Emit("MOVQ" , jit.Imm(-1), _AX) // MOVQ $-1, AX
self.Emit("MOVQ" , _AX, _VAR_sr) // MOVQ AX, sr
self.parse_string() // PARSE STRING
self.unquote_once(_VAR_sv_p, _VAR_sv_n, true) // UNQUOTE once, sv.p, sv.n
self.unquote_once(_VAR_sv_p, _VAR_sv_n, true, false) // UNQUOTE once, sv.p, sv.n
self.Emit("LEAQ" , _VAR_sv, _AX) // LEAQ sv, AX
self.Emit("XORL" , _BX, _BX) // XORL BX, BX
self.call_go(_F_strhash) // CALL_GO strhash

View file

@ -32,6 +32,7 @@ const (
_F_use_number
_F_disable_urc
_F_disable_unknown
_F_copy_string
)
// Decoder is the decoder context object
@ -110,6 +111,11 @@ func (self *Decoder) DisallowUnknownFields() {
self.f |= 1 << _F_disable_unknown
}
// CopyString causes the Decoder to decode string values by copying instead of referring.
func (self *Decoder) CopyString() {
self.f |= 1 << _F_copy_string
}
// Pretouch compiles vt ahead-of-time to avoid JIT compilation on-the-fly, in
// order to reduce the first-hit latency.
//

View file

@ -18,18 +18,18 @@ package decoder
import (
`encoding/json`
`testing`
`time`
`runtime`
`runtime/debug`
`sync`
`testing`
`time`
`github.com/bytedance/sonic/internal/rt`
`github.com/davecgh/go-spew/spew`
gojson `github.com/goccy/go-json`
`github.com/json-iterator/go`
`github.com/stretchr/testify/assert`
`github.com/stretchr/testify/require`
`github.com/bytedance/sonic/internal/rt`
)
func TestMain(m *testing.M) {
@ -53,7 +53,7 @@ func TestGC(t *testing.T) {
return
}
var w interface{}
out, err := decode(TwitterJson, &w)
out, err := decode(TwitterJson, &w, true)
if err != nil {
t.Fatal(err)
}
@ -67,7 +67,7 @@ func TestGC(t *testing.T) {
go func (wg *sync.WaitGroup) {
defer wg.Done()
var w interface{}
out, err := decode(TwitterJson, &w)
out, err := decode(TwitterJson, &w, true)
if err != nil {
t.Fatal(err)
}
@ -86,8 +86,11 @@ func init() {
_ = json.Unmarshal([]byte(TwitterJson), &_BindingValue)
}
func decode(s string, v interface{}) (int, error) {
func decode(s string, v interface{}, copy bool) (int, error) {
d := NewDecoder(s)
if copy {
d.CopyString()
}
err := d.Decode(v)
if err != nil {
return 0, err
@ -95,9 +98,127 @@ func decode(s string, v interface{}) (int, error) {
return d.i, err
}
func TestCopyString(t *testing.T) {
var data []byte
var dc *Decoder
var err error
data = []byte(`{"A":"0","B":"1"}`)
dc = NewDecoder(rt.Mem2Str(data))
dc.UseNumber()
dc.CopyString()
var obj struct{
A string
B string
}
err = dc.Decode(&obj)
if err != nil {
t.Fatal(err)
}
data[6] = '1'
if obj.A != "0" {
t.Fatal(obj)
}
data[14] = '0'
if obj.B != "1" {
t.Fatal(obj)
}
data = []byte(`{"A":"0","B":"1"}`)
dc = NewDecoder(rt.Mem2Str(data))
dc.UseNumber()
err = dc.Decode(&obj)
if err != nil {
t.Fatal(err)
}
data[6] = '1'
if obj.A != "1" {
t.Fatal(obj)
}
data[14] = '0'
if obj.B != "0" {
t.Fatal(obj)
}
data = []byte(`{"A":"0","B":"1"}`)
dc = NewDecoder(rt.Mem2Str(data))
dc.UseNumber()
dc.CopyString()
m := map[string]interface{}{}
err = dc.Decode(&m)
if err != nil {
t.Fatal(err)
}
data[2] = 'C'
data[6] = '1'
if m["A"] != "0" {
t.Fatal(m)
}
data[10] = 'D'
data[14] = '0'
if m["B"] != "1" {
t.Fatal(m)
}
data = []byte(`{"A":"0","B":"1"}`)
dc = NewDecoder(rt.Mem2Str(data))
dc.UseNumber()
m = map[string]interface{}{}
err = dc.Decode(&m)
if err != nil {
t.Fatal(err)
}
data[6] = '1'
if m["A"] != "1" {
t.Fatal(m)
}
data[14] = '0'
if m["B"] != "0" {
t.Fatal(m)
}
data = []byte(`{"A":"0","B":"1"}`)
dc = NewDecoder(rt.Mem2Str(data))
dc.UseNumber()
dc.CopyString()
var x interface{}
err = dc.Decode(&x)
if err != nil {
t.Fatal(err)
}
data[2] = 'C'
data[6] = '1'
m = x.(map[string]interface{})
if m["A"] != "0" {
t.Fatal(m)
}
data[10] = 'D'
data[14] = '0'
if m["B"] != "1" {
t.Fatal(m)
}
data = []byte(`{"A":"0","B":"1"}`)
dc = NewDecoder(rt.Mem2Str(data))
dc.UseNumber()
var y interface{}
err = dc.Decode(&y)
if err != nil {
t.Fatal(err)
}
m = y.(map[string]interface{})
data[6] = '1'
if m["A"] != "1" {
t.Fatal(m)
}
data[14] = '0'
if m["B"] != "0" {
t.Fatal(m)
}
}
func TestDecoder_Basic(t *testing.T) {
var v int
pos, err := decode("12345", &v)
pos, err := decode("12345", &v, false)
assert.NoError(t, err)
assert.Equal(t, 5, pos)
assert.Equal(t, 12345, v)
@ -105,7 +226,7 @@ func TestDecoder_Basic(t *testing.T) {
func TestDecoder_Generic(t *testing.T) {
var v interface{}
pos, err := decode(TwitterJson, &v)
pos, err := decode(TwitterJson, &v, false)
assert.NoError(t, err)
assert.Equal(t, len(TwitterJson), pos)
spew.Dump(v)
@ -113,7 +234,7 @@ func TestDecoder_Generic(t *testing.T) {
func TestDecoder_Binding(t *testing.T) {
var v TwitterStruct
pos, err := decode(TwitterJson, &v)
pos, err := decode(TwitterJson, &v, false)
assert.NoError(t, err)
assert.Equal(t, len(TwitterJson), pos)
assert.Equal(t, _BindingValue, v, 0)
@ -122,7 +243,7 @@ func TestDecoder_Binding(t *testing.T) {
func TestDecoder_MapWithIndirectElement(t *testing.T) {
var v map[string]struct { A [129]byte }
_, err := decode(`{"":{"A":[1,2,3,4,5]}}`, &v)
_, err := decode(`{"":{"A":[1,2,3,4,5]}}`, &v, false)
if x, ok := err.(SyntaxError); ok {
println(x.Description())
}
@ -132,12 +253,23 @@ func TestDecoder_MapWithIndirectElement(t *testing.T) {
func BenchmarkDecoder_Generic_Sonic(b *testing.B) {
var w interface{}
_, _ = decode(TwitterJson, &w)
_, _ = decode(TwitterJson, &w, true)
b.SetBytes(int64(len(TwitterJson)))
b.ResetTimer()
for i := 0; i < b.N; i++ {
var v interface{}
_, _ = decode(TwitterJson, &v)
_, _ = decode(TwitterJson, &v, true)
}
}
func BenchmarkDecoder_Generic_Sonic_Fast(b *testing.B) {
var w interface{}
_, _ = decode(TwitterJson, &w, false)
b.SetBytes(int64(len(TwitterJson)))
b.ResetTimer()
for i := 0; i < b.N; i++ {
var v interface{}
_, _ = decode(TwitterJson, &v, false)
}
}
@ -179,12 +311,23 @@ func BenchmarkDecoder_Generic_GoJson(b *testing.B) {
func BenchmarkDecoder_Binding_Sonic(b *testing.B) {
var w TwitterStruct
_, _ = decode(TwitterJson, &w)
_, _ = decode(TwitterJson, &w, true)
b.SetBytes(int64(len(TwitterJson)))
b.ResetTimer()
for i := 0; i < b.N; i++ {
var v TwitterStruct
_, _ = decode(TwitterJson, &v)
_, _ = decode(TwitterJson, &v, true)
}
}
func BenchmarkDecoder_Binding_Sonic_Fast(b *testing.B) {
var w TwitterStruct
_, _ = decode(TwitterJson, &w, false)
b.SetBytes(int64(len(TwitterJson)))
b.ResetTimer()
for i := 0; i < b.N; i++ {
var v TwitterStruct
_, _ = decode(TwitterJson, &v, false)
}
}
@ -226,13 +369,26 @@ func BenchmarkDecoder_Binding_GoJson(b *testing.B) {
func BenchmarkDecoder_Parallel_Generic_Sonic(b *testing.B) {
var w interface{}
_, _ = decode(TwitterJson, &w)
_, _ = decode(TwitterJson, &w, true)
b.SetBytes(int64(len(TwitterJson)))
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
var v interface{}
_, _ = decode(TwitterJson, &v)
_, _ = decode(TwitterJson, &v, true)
}
})
}
func BenchmarkDecoder_Parallel_Generic_Sonic_Fast(b *testing.B) {
var w interface{}
_, _ = decode(TwitterJson, &w, false)
b.SetBytes(int64(len(TwitterJson)))
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
var v interface{}
_, _ = decode(TwitterJson, &v, false)
}
})
}
@ -281,13 +437,26 @@ func BenchmarkDecoder_Parallel_Generic_GoJson(b *testing.B) {
func BenchmarkDecoder_Parallel_Binding_Sonic(b *testing.B) {
var w TwitterStruct
_, _ = decode(TwitterJson, &w)
_, _ = decode(TwitterJson, &w, true)
b.SetBytes(int64(len(TwitterJson)))
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
var v TwitterStruct
_, _ = decode(TwitterJson, &v)
_, _ = decode(TwitterJson, &v, true)
}
})
}
func BenchmarkDecoder_Parallel_Binding_Sonic_Fast(b *testing.B) {
var w TwitterStruct
_, _ = decode(TwitterJson, &w, false)
b.SetBytes(int64(len(TwitterJson)))
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
var v TwitterStruct
_, _ = decode(TwitterJson, &v, false)
}
})
}

View file

@ -46,7 +46,7 @@ const (
_VD_args = 8 // 8 bytes for passing arguments to this functions
_VD_fargs = 64 // 64 bytes for passing arguments to other Go functions
_VD_saves = 40 // 40 bytes for saving the registers before CALL instructions
_VD_locals = 56 // 56 bytes for local variables
_VD_locals = 88 // 88 bytes for local variables
)
const (
@ -68,6 +68,13 @@ var (
_VAR_ss_Dc = jit.Ptr(_SP, _VD_fargs + _VD_saves + 48)
)
var (
_VAR_cs_LR = jit.Ptr(_SP, _VD_fargs + _VD_saves + 56)
_VAR_cs_p = jit.Ptr(_SP, _VD_fargs + _VD_saves + 64)
_VAR_cs_n = jit.Ptr(_SP, _VD_fargs + _VD_saves + 72)
_VAR_cs_d = jit.Ptr(_SP, _VD_fargs + _VD_saves + 80)
)
type _ValueDecoder struct {
jit.BaseAssembler
}
@ -365,7 +372,6 @@ func (self *_ValueDecoder) compile() {
/** V_STRING **/
self.Link("_decode_V_STRING") // _decode_V_STRING:
self.Emit("XORL", _DX, _DX) // XORL DX, DX
self.Emit("MOVQ", _VAR_ss_Iv, _CX) // MOVQ ss.Iv, CX
self.Emit("MOVQ", _IC, _AX) // MOVQ IC, AX
self.Emit("SUBQ", _CX, _AX) // SUBQ CX, AX
@ -375,7 +381,12 @@ func (self *_ValueDecoder) compile() {
self.Sjmp("JNE" , "_unquote") // JNE _unquote
self.Emit("SUBQ", jit.Imm(1), _AX) // SUBQ $1, AX
self.Emit("LEAQ", jit.Sib(_IP, _CX, 1, 0), _R8) // LEAQ (IP)(CX), R8
self.Byte(0x48, 0x8d, 0x3d) // LEAQ (PC), DI
self.Sref("_copy_string_end", 4)
self.Emit("BTQ", jit.Imm(_F_copy_string), _VAR_df)
self.Sjmp("JC", "copy_string")
self.Link("_copy_string_end")
self.Emit("XORL", _DX, _DX) // XORL DX, DX
/* strings with no escape sequences */
self.Link("_noescape") // _noescape:
self.Emit("MOVL", jit.Imm(_S_omask), _DI) // MOVL _S_omask, DI
@ -622,6 +633,30 @@ func (self *_ValueDecoder) compile() {
self.WriteRecNotAX(8 , _DI, jit.Ptr(_SI, 0), false) // MOVQ R10, (SI)
self.Sjmp("JMP" , "_array_append") // JMP _array_append
/* copy string */
self.Link("copy_string") // pointer: R8, length: AX, return addr: DI
// self.Byte(0xcc)
self.Emit("MOVQ", _R8, _VAR_cs_p)
self.Emit("MOVQ", _AX, _VAR_cs_n)
self.Emit("MOVQ", _DI, _VAR_cs_LR)
self.Emit("MOVQ", _T_byte, jit.Ptr(_SP, 0))
self.Emit("MOVQ", _AX, jit.Ptr(_SP, 8))
self.Emit("MOVQ", _AX, jit.Ptr(_SP, 16))
self.call_go(_F_makeslice)
self.Emit("MOVQ", jit.Ptr(_SP, 24), _R8)
self.Emit("MOVQ", _R8, _VAR_cs_d)
self.Emit("MOVQ", _R8, jit.Ptr(_SP, 0))
self.Emit("MOVQ", _VAR_cs_p, _R8)
self.Emit("MOVQ", _R8, jit.Ptr(_SP, 8))
self.Emit("MOVQ", _VAR_cs_n, _AX)
self.Emit("MOVQ", _AX, jit.Ptr(_SP, 16))
self.call_go(_F_memmove)
self.Emit("MOVQ", _VAR_cs_d, _R8)
self.Emit("MOVQ", _VAR_cs_n, _AX)
self.Emit("MOVQ", _VAR_cs_LR, _DI)
// self.Byte(0xcc)
self.Rjmp("JMP", _DI)
/* error handlers */
self.Link("_stack_overflow")
self.Emit("MOVL" , _E_recurse, _EP) // MOVQ _E_recurse, EP

View file

@ -46,7 +46,7 @@ const (
_VD_args = 8 // 8 bytes for passing arguments to this functions
_VD_fargs = 64 // 64 bytes for passing arguments to other Go functions
_VD_saves = 48 // 48 bytes for saving the registers before CALL instructions
_VD_locals = 64 // 64 bytes for local variables
_VD_locals = 96 // 96 bytes for local variables
)
const (
@ -75,6 +75,13 @@ type _ValueDecoder struct {
jit.BaseAssembler
}
var (
_VAR_cs_LR = jit.Ptr(_SP, _VD_fargs + _VD_saves + 64)
_VAR_cs_p = jit.Ptr(_SP, _VD_fargs + _VD_saves + 72)
_VAR_cs_n = jit.Ptr(_SP, _VD_fargs + _VD_saves + 80)
_VAR_cs_d = jit.Ptr(_SP, _VD_fargs + _VD_saves + 88)
)
func (self *_ValueDecoder) build() uintptr {
self.Init(self.compile)
return *(*uintptr)(self.LoadWithFaker("decode_value", _VD_size, _VD_args, _Decoder_Generic_Shadow))
@ -376,7 +383,6 @@ func (self *_ValueDecoder) compile() {
/** V_STRING **/
self.Link("_decode_V_STRING") // _decode_V_STRING:
self.Emit("XORL", _DX, _DX) // XORL DX, DX
self.Emit("MOVQ", _VAR_ss_Iv, _CX) // MOVQ ss.Iv, CX
self.Emit("MOVQ", _IC, _AX) // MOVQ IC, AX
self.Emit("SUBQ", _CX, _AX) // SUBQ CX, AX
@ -386,6 +392,12 @@ func (self *_ValueDecoder) compile() {
self.Sjmp("JNE" , "_unquote") // JNE _unquote
self.Emit("SUBQ", jit.Imm(1), _AX) // SUBQ $1, AX
self.Emit("LEAQ", jit.Sib(_IP, _CX, 1, 0), _R8) // LEAQ (IP)(CX), R8
self.Byte(0x48, 0x8d, 0x3d) // LEAQ (PC), DI
self.Sref("_copy_string_end", 4)
self.Emit("BTQ", jit.Imm(_F_copy_string), _VAR_df)
self.Sjmp("JC", "copy_string")
self.Link("_copy_string_end")
self.Emit("XORL", _DX, _DX)
/* strings with no escape sequences */
self.Link("_noescape") // _noescape:
@ -625,6 +637,24 @@ func (self *_ValueDecoder) compile() {
self.WriteRecNotAX(8 , _DI, jit.Ptr(_SI, 0), false) // MOVQ R10, (SI)
self.Sjmp("JMP" , "_array_append") // JMP _array_append
/* copy string */
self.Link("copy_string") // pointer: R8, length: AX, return addr: DI
self.Emit("MOVQ", _R8, _VAR_cs_p)
self.Emit("MOVQ", _AX, _VAR_cs_n)
self.Emit("MOVQ", _DI, _VAR_cs_LR)
self.Emit("MOVQ", _AX, _BX)
self.Emit("MOVQ", _AX, _CX)
self.Emit("MOVQ", _T_byte, _AX)
self.call_go(_F_makeslice)
self.Emit("MOVQ", _AX, _VAR_cs_d)
self.Emit("MOVQ", _VAR_cs_p, _BX)
self.Emit("MOVQ", _VAR_cs_n, _CX)
self.call_go(_F_memmove)
self.Emit("MOVQ", _VAR_cs_d, _R8)
self.Emit("MOVQ", _VAR_cs_n, _AX)
self.Emit("MOVQ", _VAR_cs_LR, _DI)
self.Rjmp("JMP", _DI)
/* error handlers */
self.Link("_stack_overflow")
self.Emit("MOVL" , _E_recurse, _EP) // MOVQ _E_recurse, EP

82
decoder/norace_test.go Normal file
View file

@ -0,0 +1,82 @@
// +build !race
/*
* 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 decoder
import (
`testing`
`unsafe`
`runtime`
`github.com/bytedance/sonic/internal/rt`
)
var referred = false
func TestStringReferring(t *testing.T) {
str := []byte(`{"A":"0","B":"1"}`)
sp := *(**byte)(unsafe.Pointer(&str))
println("malloc *byte ", sp)
runtime.SetFinalizer(sp, func(sp *byte){
referred = false
println("*byte ", sp, " got free")
})
runtime.GC()
println("first GC")
var obj struct{
A string
B string
}
dc := NewDecoder(rt.Mem2Str(str))
dc.CopyString()
referred = true
if err := dc.Decode(&obj); err != nil {
t.Fatal(err)
}
runtime.GC()
println("second GC")
if referred {
t.Fatal("*byte is being referred")
}
str2 := []byte(`{"A":"0","B":"1"}`)
sp2 := *(**byte)(unsafe.Pointer(&str2))
println("malloc *byte ", sp2)
runtime.SetFinalizer(sp2, func(sp *byte){
referred = false
println("*byte ", sp, " got free")
})
runtime.GC()
println("first GC")
var obj2 interface{}
dc2 := NewDecoder(rt.Mem2Str(str2))
dc2.UseNumber()
dc2.CopyString()
referred = true
if err := dc2.Decode(&obj2); err != nil {
t.Fatal(err)
}
runtime.GC()
println("second GC")
if referred {
t.Fatal("*byte is being referred")
}
runtime.KeepAlive(&obj)
runtime.KeepAlive(&obj2)
}

View file

@ -58,6 +58,11 @@ func convTstring(v string) unsafe.Pointer
//goland:noinspection GoUnusedParameter
func memequal(a unsafe.Pointer, b unsafe.Pointer, size uintptr) bool
//go:noescape
//go:linkname memmove runtime.memmove
//goland:noinspection GoUnusedParameter
func memmove(to unsafe.Pointer, from unsafe.Pointer, n uintptr)
//go:linkname mallocgc runtime.mallocgc
//goland:noinspection GoUnusedParameter
func mallocgc(size uintptr, typ *rt.GoType, needzero bool) unsafe.Pointer

View file

@ -25,7 +25,6 @@ import (
`github.com/bytedance/sonic/encoder`
`github.com/bytedance/sonic/option`
`github.com/bytedance/sonic/internal/native/types`
`github.com/bytedance/sonic/internal/rt`
)
// Marshal returns the JSON encoding of v.
@ -35,8 +34,10 @@ func Marshal(val interface{}) ([]byte, error) {
// Unmarshal parses the JSON-encoded data and stores the result in the value
// pointed to by v.
// NOTICE: This API copies given buffer by default.
// if you want to pass JSON more effecient, use UnmarshalString instead.
func Unmarshal(buf []byte, val interface{}) error {
return UnmarshalString(rt.Mem2Str(buf), val)
return UnmarshalString(string(buf), val)
}
// UnmarshalString is like Unmarshal, except buf is a string.