mirror of
https://github.com/ii64/sonic.git
synced 2026-06-21 00:46:43 +08:00
opt: use simd to optimize htmlescape (#171)
* opt: use simd to optimize htmlescape * opt: reuse escaped buffer * feat: cmake with Clang12 Co-authored-by: liuqiang <liuqiang.06@bytedance.com> Co-authored-by: duanyi.aster <duanyi.aster@bytedance.com>
This commit is contained in:
parent
14121d64f1
commit
d75ce3f730
16 changed files with 5203 additions and 3317 deletions
|
|
@ -21,7 +21,9 @@ import (
|
|||
`encoding/json`
|
||||
`reflect`
|
||||
`runtime`
|
||||
`unsafe`
|
||||
|
||||
`github.com/bytedance/sonic/internal/native`
|
||||
`github.com/bytedance/sonic/internal/rt`
|
||||
`github.com/bytedance/sonic/option`
|
||||
)
|
||||
|
|
@ -102,7 +104,6 @@ func Encode(val interface{}, opts Options) ([]byte, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
/* EscapeHTML has already returned a new buffer*/
|
||||
if opts & EscapeHTML != 0 {
|
||||
return buf, nil
|
||||
}
|
||||
|
|
@ -131,10 +132,9 @@ func EncodeInto(buf *[]byte, val interface{}, opts Options) error {
|
|||
|
||||
/* EscapeHTML needs to allocate a new buffer*/
|
||||
if opts & EscapeHTML != 0 {
|
||||
dst := bytes.NewBuffer(make([]byte, 0, len(*buf)))
|
||||
json.HTMLEscape(dst, *buf)
|
||||
freeBytes(*buf)
|
||||
*buf = dst.Bytes()
|
||||
dest := HTMLEscape(nil, *buf)
|
||||
freeBytes(*buf) // free origin used buffer
|
||||
*buf = dest
|
||||
}
|
||||
|
||||
/* avoid GC ahead */
|
||||
|
|
@ -143,6 +143,48 @@ func EncodeInto(buf *[]byte, val interface{}, opts Options) error {
|
|||
return err
|
||||
}
|
||||
|
||||
var typeByte = rt.UnpackType(reflect.TypeOf(byte(0)))
|
||||
|
||||
// HTMLEscape appends to dst the JSON-encoded src with <, >, &, U+2028 and U+2029
|
||||
// characters inside string literals changed to \u003c, \u003e, \u0026, \u2028, \u2029
|
||||
// so that the JSON will be safe to embed inside HTML <script> tags.
|
||||
// For historical reasons, web browsers don't honor standard HTML
|
||||
// escaping within <script> tags, so an alternative JSON encoding must
|
||||
// be used.
|
||||
func HTMLEscape(dest []byte, src []byte) []byte {
|
||||
nb := len(src)
|
||||
|
||||
// initilize dest buffer
|
||||
cap := nb * 6 / 5
|
||||
if dest == nil {
|
||||
dest = make([]byte, 0, cap)
|
||||
}
|
||||
ds := (*rt.GoSlice)(unsafe.Pointer(&dest))
|
||||
sp := (*rt.GoSlice)(unsafe.Pointer(&src)).Ptr
|
||||
ds.Len = 0
|
||||
if (ds.Cap < cap) {
|
||||
*ds = growslice(typeByte, *ds, cap)
|
||||
}
|
||||
|
||||
for nb > 0 {
|
||||
dp := unsafe.Pointer(uintptr(ds.Ptr) + uintptr(ds.Len))
|
||||
dn := ds.Cap - ds.Len
|
||||
|
||||
ret := native.HTMLEscape(sp, nb, dp, &dn)
|
||||
ds.Len += dn
|
||||
|
||||
if ret >= 0 {
|
||||
break
|
||||
}
|
||||
ret = ^ret
|
||||
nb -= ret
|
||||
|
||||
*ds = growslice(typeByte, *ds, ds.Cap * 2)
|
||||
sp = unsafe.Pointer(uintptr(sp) + uintptr(ret))
|
||||
}
|
||||
return dest
|
||||
}
|
||||
|
||||
// EncodeIndented is like Encode but applies Indent to format the output.
|
||||
// Each JSON element in the output will begin on a new line beginning with prefix
|
||||
// followed by one or more copies of indent according to the indentation nesting.
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package encoder
|
||||
|
||||
import (
|
||||
`bytes`
|
||||
`encoding/json`
|
||||
`runtime`
|
||||
`runtime/debug`
|
||||
|
|
@ -190,6 +191,14 @@ func TestEncoder_EscapeHTML(t *testing.T) {
|
|||
require.Equal(t, `{"&&":{"X":"<>"}}`, string(ret))
|
||||
}
|
||||
|
||||
func TestEncoder_EscapeHTML_LargeJson(t *testing.T) {
|
||||
buf1, err1 := Encode(&_BindingValue, SortMapKeys | EscapeHTML)
|
||||
require.NoError(t, err1)
|
||||
buf2, err2 :=json.Marshal(&_BindingValue)
|
||||
require.NoError(t, err2)
|
||||
require.Equal(t, buf1, buf2)
|
||||
}
|
||||
|
||||
var _GenericValue interface{}
|
||||
var _BindingValue TwitterStruct
|
||||
|
||||
|
|
@ -287,6 +296,17 @@ func BenchmarkEncoder_Binding_SonicSorted(b *testing.B) {
|
|||
}
|
||||
}
|
||||
|
||||
func BenchmarkEncoder_Binding_Sonic_Std(b *testing.B) {
|
||||
_, _ = Encode(&_BindingValue, 0)
|
||||
b.SetBytes(int64(len(TwitterJson)))
|
||||
b.ResetTimer()
|
||||
var buf []byte
|
||||
for i := 0; i < b.N; i++ {
|
||||
buf, _ = Encode(&_BindingValue, SortMapKeys | EscapeHTML)
|
||||
}
|
||||
_ = buf
|
||||
}
|
||||
|
||||
func BenchmarkEncoder_Binding_JsonIter(b *testing.B) {
|
||||
_, _ = jsoniter.Marshal(&_BindingValue)
|
||||
b.SetBytes(int64(len(TwitterJson)))
|
||||
|
|
@ -423,3 +443,27 @@ func BenchmarkEncoder_Parallel_Binding_StdLib(b *testing.B) {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkHTMLEscape_Sonic(b *testing.B) {
|
||||
jsonByte := []byte(TwitterJson)
|
||||
b.SetBytes(int64(len(TwitterJson)))
|
||||
b.ResetTimer()
|
||||
var buf []byte
|
||||
for i := 0; i < b.N; i++ {
|
||||
buf = HTMLEscape(nil, jsonByte)
|
||||
}
|
||||
_ = buf
|
||||
}
|
||||
|
||||
func BenchmarkHTMLEscape_StdLib(b *testing.B) {
|
||||
jsonByte := []byte(TwitterJson)
|
||||
b.SetBytes(int64(len(TwitterJson)))
|
||||
b.ResetTimer()
|
||||
var buf []byte
|
||||
for i := 0; i < b.N; i++ {
|
||||
out := bytes.NewBuffer(make([]byte, 0, len(TwitterJson) * 6 / 5))
|
||||
json.HTMLEscape(out, jsonByte)
|
||||
buf = out.Bytes()
|
||||
}
|
||||
_ = buf
|
||||
}
|
||||
|
|
@ -54,6 +54,11 @@ func __lspace(sp unsafe.Pointer, nb int, off int) (ret int)
|
|||
//goland:noinspection GoUnusedParameter
|
||||
func __quote(sp unsafe.Pointer, nb int, dp unsafe.Pointer, dn *int, flags uint64) (ret int)
|
||||
|
||||
//go:nosplit
|
||||
//go:noescape
|
||||
//goland:noinspection GoUnusedParameter
|
||||
func __html_escape(sp unsafe.Pointer, nb int, dp unsafe.Pointer, dn *int) (ret int)
|
||||
|
||||
//go:nosplit
|
||||
//go:noescape
|
||||
//goland:noinspection GoUnusedParameter
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -208,6 +208,31 @@ func TestNative_UnquoteUnicodeReplacement(t *testing.T) {
|
|||
assert.Equal(t, "hello\ufffd\ufffdworld", string(d))
|
||||
}
|
||||
|
||||
func TestNative_HTMLEscape(t *testing.T) {
|
||||
s := "hello\u2029\u2028<&>world"
|
||||
d := make([]byte, 256)
|
||||
dp := (*rt.GoSlice)(unsafe.Pointer(&d))
|
||||
sp := (*rt.GoString)(unsafe.Pointer(&s))
|
||||
rv := __html_escape(sp.Ptr, sp.Len, dp.Ptr, &dp.Len)
|
||||
if rv < 0 {
|
||||
require.NoError(t, types.ParsingError(-rv))
|
||||
}
|
||||
assert.Equal(t, len(s), rv)
|
||||
assert.Equal(t, 40, len(d))
|
||||
assert.Equal(t, `hello\u2029\u2028\u003c\u0026\u003eworld`, string(d))
|
||||
}
|
||||
|
||||
func TestNative_HTMLEscapeNoMem(t *testing.T) {
|
||||
s := "hello\u2029\u2028<&>world"
|
||||
d := make([]byte, 10)
|
||||
dp := (*rt.GoSlice)(unsafe.Pointer(&d))
|
||||
sp := (*rt.GoString)(unsafe.Pointer(&s))
|
||||
rv := __html_escape(sp.Ptr, sp.Len, dp.Ptr, &dp.Len)
|
||||
assert.Equal(t, -8, rv)
|
||||
assert.Equal(t, 5, len(d))
|
||||
assert.Equal(t, `hello`, string(d))
|
||||
}
|
||||
|
||||
func TestNative_Vstring(t *testing.T) {
|
||||
var v types.JsonState
|
||||
i := 0
|
||||
|
|
|
|||
|
|
@ -10,28 +10,30 @@ func __native_entry__() uintptr
|
|||
|
||||
var (
|
||||
_subr__f64toa = __native_entry__() + 630
|
||||
_subr__html_escape = __native_entry__() + 8160
|
||||
_subr__i64toa = __native_entry__() + 3642
|
||||
_subr__lspace = __native_entry__() + 301
|
||||
_subr__lzero = __native_entry__() + 13
|
||||
_subr__quote = __native_entry__() + 4955
|
||||
_subr__skip_array = __native_entry__() + 16074
|
||||
_subr__skip_object = __native_entry__() + 16109
|
||||
_subr__skip_one = __native_entry__() + 14295
|
||||
_subr__skip_array = __native_entry__() + 17223
|
||||
_subr__skip_object = __native_entry__() + 17258
|
||||
_subr__skip_one = __native_entry__() + 15444
|
||||
_subr__u64toa = __native_entry__() + 3735
|
||||
_subr__unquote = __native_entry__() + 5888
|
||||
_subr__value = __native_entry__() + 9657
|
||||
_subr__vnumber = __native_entry__() + 12453
|
||||
_subr__vsigned = __native_entry__() + 13767
|
||||
_subr__vstring = __native_entry__() + 11418
|
||||
_subr__vunsigned = __native_entry__() + 14026
|
||||
_subr__unquote = __native_entry__() + 6005
|
||||
_subr__value = __native_entry__() + 10806
|
||||
_subr__vnumber = __native_entry__() + 13602
|
||||
_subr__vsigned = __native_entry__() + 14916
|
||||
_subr__vstring = __native_entry__() + 12567
|
||||
_subr__vunsigned = __native_entry__() + 15175
|
||||
)
|
||||
|
||||
const (
|
||||
_stack__f64toa = 120
|
||||
_stack__html_escape = 72
|
||||
_stack__i64toa = 24
|
||||
_stack__lspace = 8
|
||||
_stack__lzero = 8
|
||||
_stack__quote = 64
|
||||
_stack__quote = 80
|
||||
_stack__skip_array = 136
|
||||
_stack__skip_object = 136
|
||||
_stack__skip_one = 136
|
||||
|
|
@ -46,6 +48,7 @@ const (
|
|||
|
||||
var (
|
||||
_ = _subr__f64toa
|
||||
_ = _subr__html_escape
|
||||
_ = _subr__i64toa
|
||||
_ = _subr__lspace
|
||||
_ = _subr__lzero
|
||||
|
|
@ -64,6 +67,7 @@ var (
|
|||
|
||||
const (
|
||||
_ = _stack__f64toa
|
||||
_ = _stack__html_escape
|
||||
_ = _stack__i64toa
|
||||
_ = _stack__lspace
|
||||
_ = _stack__lzero
|
||||
|
|
|
|||
|
|
@ -54,6 +54,11 @@ func __lspace(sp unsafe.Pointer, nb int, off int) (ret int)
|
|||
//goland:noinspection GoUnusedParameter
|
||||
func __quote(sp unsafe.Pointer, nb int, dp unsafe.Pointer, dn *int, flags uint64) (ret int)
|
||||
|
||||
//go:nosplit
|
||||
//go:noescape
|
||||
//goland:noinspection GoUnusedParameter
|
||||
func __html_escape(sp unsafe.Pointer, nb int, dp unsafe.Pointer, dn *int) (ret int)
|
||||
|
||||
//go:nosplit
|
||||
//go:noescape
|
||||
//goland:noinspection GoUnusedParameter
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -208,6 +208,31 @@ func TestNative_UnquoteUnicodeReplacement(t *testing.T) {
|
|||
assert.Equal(t, "hello\ufffd\ufffdworld", string(d))
|
||||
}
|
||||
|
||||
func TestNative_HTMLEscape(t *testing.T) {
|
||||
s := "hello\u2029\u2028<&>world"
|
||||
d := make([]byte, 256)
|
||||
dp := (*rt.GoSlice)(unsafe.Pointer(&d))
|
||||
sp := (*rt.GoString)(unsafe.Pointer(&s))
|
||||
rv := __html_escape(sp.Ptr, sp.Len, dp.Ptr, &dp.Len)
|
||||
if rv < 0 {
|
||||
require.NoError(t, types.ParsingError(-rv))
|
||||
}
|
||||
assert.Equal(t, len(s), rv)
|
||||
assert.Equal(t, 40, len(d))
|
||||
assert.Equal(t, `hello\u2029\u2028\u003c\u0026\u003eworld`, string(d))
|
||||
}
|
||||
|
||||
func TestNative_HTMLEscapeNoMem(t *testing.T) {
|
||||
s := "hello\u2029\u2028<&>world"
|
||||
d := make([]byte, 10)
|
||||
dp := (*rt.GoSlice)(unsafe.Pointer(&d))
|
||||
sp := (*rt.GoString)(unsafe.Pointer(&s))
|
||||
rv := __html_escape(sp.Ptr, sp.Len, dp.Ptr, &dp.Len)
|
||||
assert.Equal(t, -8, rv)
|
||||
assert.Equal(t, 5, len(d))
|
||||
assert.Equal(t, `hello`, string(d))
|
||||
}
|
||||
|
||||
func TestNative_Vstring(t *testing.T) {
|
||||
var v types.JsonState
|
||||
i := 0
|
||||
|
|
|
|||
|
|
@ -10,28 +10,30 @@ func __native_entry__() uintptr
|
|||
|
||||
var (
|
||||
_subr__f64toa = __native_entry__() + 903
|
||||
_subr__html_escape = __native_entry__() + 9535
|
||||
_subr__i64toa = __native_entry__() + 3915
|
||||
_subr__lspace = __native_entry__() + 429
|
||||
_subr__lzero = __native_entry__() + 13
|
||||
_subr__quote = __native_entry__() + 5328
|
||||
_subr__skip_array = __native_entry__() + 19163
|
||||
_subr__skip_object = __native_entry__() + 19198
|
||||
_subr__skip_one = __native_entry__() + 16306
|
||||
_subr__skip_array = __native_entry__() + 21058
|
||||
_subr__skip_object = __native_entry__() + 21093
|
||||
_subr__skip_one = __native_entry__() + 18201
|
||||
_subr__u64toa = __native_entry__() + 4008
|
||||
_subr__unquote = __native_entry__() + 7125
|
||||
_subr__value = __native_entry__() + 11812
|
||||
_subr__vnumber = __native_entry__() + 14464
|
||||
_subr__vsigned = __native_entry__() + 15778
|
||||
_subr__vstring = __native_entry__() + 13587
|
||||
_subr__vunsigned = __native_entry__() + 16037
|
||||
_subr__unquote = __native_entry__() + 7080
|
||||
_subr__value = __native_entry__() + 13707
|
||||
_subr__vnumber = __native_entry__() + 16359
|
||||
_subr__vsigned = __native_entry__() + 17673
|
||||
_subr__vstring = __native_entry__() + 15482
|
||||
_subr__vunsigned = __native_entry__() + 17932
|
||||
)
|
||||
|
||||
const (
|
||||
_stack__f64toa = 120
|
||||
_stack__html_escape = 56
|
||||
_stack__i64toa = 24
|
||||
_stack__lspace = 8
|
||||
_stack__lzero = 8
|
||||
_stack__quote = 80
|
||||
_stack__quote = 64
|
||||
_stack__skip_array = 128
|
||||
_stack__skip_object = 128
|
||||
_stack__skip_one = 128
|
||||
|
|
@ -46,6 +48,7 @@ const (
|
|||
|
||||
var (
|
||||
_ = _subr__f64toa
|
||||
_ = _subr__html_escape
|
||||
_ = _subr__i64toa
|
||||
_ = _subr__lspace
|
||||
_ = _subr__lzero
|
||||
|
|
@ -64,6 +67,7 @@ var (
|
|||
|
||||
const (
|
||||
_ = _stack__f64toa
|
||||
_ = _stack__html_escape
|
||||
_ = _stack__i64toa
|
||||
_ = _stack__lspace
|
||||
_ = _stack__lzero
|
||||
|
|
|
|||
|
|
@ -68,6 +68,11 @@ func Quote(s unsafe.Pointer, nb int, dp unsafe.Pointer, dn *int, flags uint64) i
|
|||
//goland:noinspection GoUnusedParameter
|
||||
func Unquote(s unsafe.Pointer, nb int, dp unsafe.Pointer, ep *int, flags uint64) int
|
||||
|
||||
//go:nosplit
|
||||
//go:noescape
|
||||
//goland:noinspection GoUnusedParameter
|
||||
func HTMLEscape(s unsafe.Pointer, nb int, dp unsafe.Pointer, dn *int) int
|
||||
|
||||
//go:nosplit
|
||||
//go:noescape
|
||||
//goland:noinspection GoUnusedParameter
|
||||
|
|
|
|||
|
|
@ -36,6 +36,12 @@ TEXT ·Unquote(SB), NOSPLIT, $0 - 48
|
|||
JMP github·com∕bytedance∕sonic∕internal∕native∕avx2·__unquote(SB)
|
||||
JMP github·com∕bytedance∕sonic∕internal∕native∕avx·__unquote(SB)
|
||||
|
||||
TEXT ·HTMLEscape(SB), NOSPLIT, $0 - 40
|
||||
CMPB github·com∕bytedance∕sonic∕internal∕cpu·HasAVX2(SB), $0
|
||||
JE 2(PC)
|
||||
JMP github·com∕bytedance∕sonic∕internal∕native∕avx2·__html_escape(SB)
|
||||
JMP github·com∕bytedance∕sonic∕internal∕native∕avx·__html_escape(SB)
|
||||
|
||||
TEXT ·Value(SB), NOSPLIT, $0 - 48
|
||||
CMPB github·com∕bytedance∕sonic∕internal∕cpu·HasAVX2(SB), $0
|
||||
JE 2(PC)
|
||||
|
|
|
|||
|
|
@ -52,6 +52,11 @@ func __lspace(sp unsafe.Pointer, nb int, off int) (ret int)
|
|||
//goland:noinspection GoUnusedParameter
|
||||
func __quote(sp unsafe.Pointer, nb int, dp unsafe.Pointer, dn *int, flags uint64) (ret int)
|
||||
|
||||
//go:nosplit
|
||||
//go:noescape
|
||||
//goland:noinspection GoUnusedParameter
|
||||
func __html_escape(sp unsafe.Pointer, nb int, dp unsafe.Pointer, dn *int) (ret int)
|
||||
|
||||
//go:nosplit
|
||||
//go:noescape
|
||||
//goland:noinspection GoUnusedParameter
|
||||
|
|
|
|||
|
|
@ -206,6 +206,31 @@ func TestNative_UnquoteUnicodeReplacement(t *testing.T) {
|
|||
assert.Equal(t, "hello\ufffd\ufffdworld", string(d))
|
||||
}
|
||||
|
||||
func TestNative_HTMLEscape(t *testing.T) {
|
||||
s := "hello\u2029\u2028<&>world"
|
||||
d := make([]byte, 256)
|
||||
dp := (*rt.GoSlice)(unsafe.Pointer(&d))
|
||||
sp := (*rt.GoString)(unsafe.Pointer(&s))
|
||||
rv := __html_escape(sp.Ptr, sp.Len, dp.Ptr, &dp.Len)
|
||||
if rv < 0 {
|
||||
require.NoError(t, types.ParsingError(-rv))
|
||||
}
|
||||
assert.Equal(t, len(s), rv)
|
||||
assert.Equal(t, 40, len(d))
|
||||
assert.Equal(t, `hello\u2029\u2028\u003c\u0026\u003eworld`, string(d))
|
||||
}
|
||||
|
||||
func TestNative_HTMLEscapeNoMem(t *testing.T) {
|
||||
s := "hello\u2029\u2028<&>world"
|
||||
d := make([]byte, 10)
|
||||
dp := (*rt.GoSlice)(unsafe.Pointer(&d))
|
||||
sp := (*rt.GoString)(unsafe.Pointer(&s))
|
||||
rv := __html_escape(sp.Ptr, sp.Len, dp.Ptr, &dp.Len)
|
||||
assert.Equal(t, -8, rv)
|
||||
assert.Equal(t, 5, len(d))
|
||||
assert.Equal(t, `hello`, string(d))
|
||||
}
|
||||
|
||||
func TestNative_Vstring(t *testing.T) {
|
||||
var v types.JsonState
|
||||
i := 0
|
||||
|
|
|
|||
|
|
@ -106,6 +106,7 @@ size_t lspace(const char *sp, size_t nb, size_t p);
|
|||
|
||||
ssize_t quote(const char *sp, ssize_t nb, char *dp, ssize_t *dn, uint64_t flags);
|
||||
ssize_t unquote(const char *sp, ssize_t nb, char *dp, ssize_t *ep, uint64_t flags);
|
||||
ssize_t html_escape(const char *sp, ssize_t nb, char *dp, ssize_t *dn);
|
||||
|
||||
long value(const char *s, size_t n, long p, JsonState *ret, int allow_control);
|
||||
void vstring(const GoString *src, long *p, JsonState *ret);
|
||||
|
|
|
|||
192
native/parsing.c
192
native/parsing.c
|
|
@ -97,7 +97,17 @@ static const quoted_t _DoubleQuoteTab[256] = {
|
|||
['\\' ] = { .n = 4, .s = "\\\\\\\\" },
|
||||
};
|
||||
|
||||
static inline void memcpy_p8(char *dp, const char *sp, size_t nb) {
|
||||
static const quoted_t _HtmlQuoteTab[256] = {
|
||||
['<'] = { .n = 6, .s = "\\u003c" },
|
||||
['>'] = { .n = 6, .s = "\\u003e" },
|
||||
['&'] = { .n = 6, .s = "\\u0026" },
|
||||
// \u2028 and \u2029 is [E2 80 A8] and [E2 80 A9]
|
||||
[0xe2] = { .n = 0, .s = {0} },
|
||||
[0xa8] = { .n = 6, .s = "\\u2028" },
|
||||
[0xa9] = { .n = 6, .s = "\\u2029" },
|
||||
};
|
||||
|
||||
static inline void memcpy_p8(char *dp, const char *sp, ssize_t nb) {
|
||||
if (nb >= 4) { *(uint32_t *)dp = *(const uint32_t *)sp; sp += 4, dp += 4, nb -= 4; }
|
||||
if (nb >= 2) { *(uint16_t *)dp = *(const uint16_t *)sp; sp += 2, dp += 2, nb -= 2; }
|
||||
if (nb >= 1) { *dp = *sp; }
|
||||
|
|
@ -621,3 +631,183 @@ ssize_t unquote(const char *sp, ssize_t nb, char *dp, ssize_t *ep, uint64_t flag
|
|||
/* calculate the result length */
|
||||
return dp + nb - p;
|
||||
}
|
||||
|
||||
static inline __m128i _mm_find_html(__m128i vv) {
|
||||
__m128i e1 = _mm_cmpeq_epi8 (vv, _mm_set1_epi8('<'));
|
||||
__m128i e2 = _mm_cmpeq_epi8 (vv, _mm_set1_epi8('>'));
|
||||
__m128i e3 = _mm_cmpeq_epi8 (vv, _mm_set1_epi8('&'));
|
||||
__m128i e4 = _mm_cmpeq_epi8 (vv, _mm_set1_epi8('\xe2'));
|
||||
__m128i r1 = _mm_or_si128 (e1, e2);
|
||||
__m128i r2 = _mm_or_si128 (e3, e4);
|
||||
__m128i rv = _mm_or_si128 (r1, r2);
|
||||
return rv;
|
||||
}
|
||||
|
||||
#if USE_AVX2
|
||||
static inline __m256i _mm256_find_html(__m256i vv) {
|
||||
__m256i e1 = _mm256_cmpeq_epi8 (vv, _mm256_set1_epi8('<'));
|
||||
__m256i e2 = _mm256_cmpeq_epi8 (vv, _mm256_set1_epi8('>'));
|
||||
__m256i e3 = _mm256_cmpeq_epi8 (vv, _mm256_set1_epi8('&'));
|
||||
__m256i e4 = _mm256_cmpeq_epi8 (vv, _mm256_set1_epi8('\xe2'));
|
||||
__m256i r1 = _mm256_or_si256 (e1, e2);
|
||||
__m256i r2 = _mm256_or_si256 (e3, e4);
|
||||
__m256i rv = _mm256_or_si256 (r1, r2);
|
||||
return rv;
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline ssize_t memcchr_html_quote(const char *sp, ssize_t nb, char *dp, ssize_t dn) {
|
||||
uint32_t mm;
|
||||
const char * ss = sp;
|
||||
|
||||
#if USE_AVX2
|
||||
/* 32-byte loop, full store */
|
||||
while (nb >= 32 && dn >= 32) {
|
||||
__m256i vv = _mm256_loadu_si256 ((const void *)sp);
|
||||
__m256i rv = _mm256_find_html (vv);
|
||||
_mm256_storeu_si256 ((void *)dp, vv);
|
||||
|
||||
/* check for matches */
|
||||
if ((mm = _mm256_movemask_epi8(rv)) != 0) {
|
||||
return sp - ss + __builtin_ctz(mm);
|
||||
}
|
||||
|
||||
/* move to next block */
|
||||
sp += 32;
|
||||
dp += 32;
|
||||
nb -= 32;
|
||||
dn -= 32;
|
||||
}
|
||||
|
||||
/* 32-byte test, partial store */
|
||||
if (nb >= 32) {
|
||||
__m256i vv = _mm256_loadu_si256 ((const void *)sp);
|
||||
__m256i rv = _mm256_find_html (vv);
|
||||
uint32_t mv = _mm256_movemask_epi8 (rv);
|
||||
uint32_t fv = __builtin_ctzll ((uint64_t)mv | 0x0100000000);
|
||||
|
||||
/* copy at most `dn` characters */
|
||||
if (fv <= dn) {
|
||||
memcpy_p32(dp, sp, fv);
|
||||
return sp - ss + fv;
|
||||
} else {
|
||||
memcpy_p32(dp, sp, dn);
|
||||
return -(sp - ss + dn) - 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* clear upper half to avoid AVX-SSE transition penalty */
|
||||
_mm256_zeroupper();
|
||||
#endif
|
||||
|
||||
/* 16-byte loop, full store */
|
||||
while (nb >= 16 && dn >= 16) {
|
||||
__m128i vv = _mm_loadu_si128 ((const void *)sp);
|
||||
__m128i rv = _mm_find_html (vv);
|
||||
_mm_storeu_si128 ((void *)dp, vv);
|
||||
|
||||
/* check for matches */
|
||||
if ((mm = _mm_movemask_epi8(rv)) != 0) {
|
||||
return sp - ss + __builtin_ctz(mm);
|
||||
}
|
||||
|
||||
/* move to next block */
|
||||
sp += 16;
|
||||
dp += 16;
|
||||
nb -= 16;
|
||||
dn -= 16;
|
||||
}
|
||||
|
||||
/* 16-byte test, partial store */
|
||||
if (nb >= 16) {
|
||||
__m128i vv = _mm_loadu_si128 ((const void *)sp);
|
||||
__m128i rv = _mm_find_html (vv);
|
||||
uint32_t mv = _mm_movemask_epi8 (rv);
|
||||
uint32_t fv = __builtin_ctz (mv | 0x010000);
|
||||
|
||||
/* copy at most `dn` characters */
|
||||
if (fv <= dn) {
|
||||
memcpy_p16(dp, sp, fv);
|
||||
return sp - ss + fv;
|
||||
} else {
|
||||
memcpy_p16(dp, sp, dn);
|
||||
return -(sp - ss + dn) - 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* handle the remaining bytes with scalar code */
|
||||
while (nb > 0 && dn > 0) {
|
||||
if (*sp == '<' || *sp == '>' || *sp == '&' || *sp == '\xe2') {
|
||||
return sp - ss;
|
||||
} else {
|
||||
dn--, nb--;
|
||||
*dp++ = *sp++;
|
||||
}
|
||||
}
|
||||
|
||||
/* check for dest buffer */
|
||||
if (nb == 0) {
|
||||
return sp - ss;
|
||||
} else {
|
||||
return -(sp - ss) - 1;
|
||||
}
|
||||
}
|
||||
|
||||
ssize_t html_escape(const char *sp, ssize_t nb, char *dp, ssize_t *dn) {
|
||||
ssize_t nd = *dn;
|
||||
const char * ds = dp;
|
||||
const char * ss = sp;
|
||||
const quoted_t * tab = _HtmlQuoteTab;
|
||||
|
||||
/* find the special characters, copy on the fly */
|
||||
while (nb != 0) {
|
||||
int nc = 0;
|
||||
uint8_t ch = 0;
|
||||
ssize_t rb = memcchr_html_quote(sp, nb, dp, nd);
|
||||
|
||||
/* not enough buffer space */
|
||||
if (rb < 0) {
|
||||
*dn = dp - ds - rb - 1;
|
||||
return -(sp - ss - rb - 1) - 1;
|
||||
}
|
||||
|
||||
/* skip already copied bytes */
|
||||
sp += rb;
|
||||
dp += rb;
|
||||
nb -= rb;
|
||||
nd -= rb;
|
||||
|
||||
/* stop if already finished */
|
||||
if (nb <= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* check for \u2028 and \u2029, [e2 80 a8] and [e2 80 a9] */
|
||||
if (nb >= 3 && 0xa880e2 == (*(uint32_t *)sp & 0xfeffff)) {
|
||||
sp += 2;
|
||||
nb -= 2;
|
||||
}
|
||||
|
||||
/* get the escape entry, handle consecutive quotes */
|
||||
ch = * (uint8_t*) sp;
|
||||
nc = tab[ch].n;
|
||||
|
||||
|
||||
/* check for buffer space */
|
||||
if (nd < nc) {
|
||||
*dn = dp - ds;
|
||||
return -(sp - ss) - 1;
|
||||
}
|
||||
|
||||
/* copy the quoted value */
|
||||
memcpy_p8(dp, tab[ch].s, nc);
|
||||
sp++;
|
||||
nb--;
|
||||
dp += nc;
|
||||
nd -= nc;
|
||||
}
|
||||
|
||||
/* all done */
|
||||
*dn = dp - ds;
|
||||
return sp - ss;
|
||||
}
|
||||
Loading…
Reference in a new issue