| #include <arm_neon.h> |
| |
| /* This code is almost the same as SSE implementation, please reference |
| * utf8-range-sse.inc for detailed explanation. |
| * The only difference is the range adjustment step. NEON code is more |
| * straightforward. |
| */ |
| |
| static FORCE_INLINE_ATTR size_t utf8_range_ValidateUTF8Simd( |
| const char* data_original, const char* data, const char* end, |
| int return_position) { |
| const uint8x16_t first_len_tbl = { |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, |
| }; |
| const uint8x16_t first_range_tbl = { |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, |
| }; |
| const uint8x16_t range_min_tbl = { |
| 0x00, 0x80, 0x80, 0x80, 0xA0, 0x80, 0x90, 0x80, |
| 0xC2, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, |
| }; |
| const uint8x16_t range_max_tbl = { |
| 0x7F, 0xBF, 0xBF, 0xBF, 0xBF, 0x9F, 0xBF, 0x8F, |
| 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| }; |
| /* Range adjustment in NEON uint8x16x2 table. Note that lanes are interleaved |
| * in register. The table below is plotted vertically to ease understanding. |
| * The 1st column is for E0~EF, 2nd column for F0~FF. |
| */ |
| // clang-format off |
| const uint8_t range_adjust_tbl_data[] = { |
| /* index -> 0~15 16~31 <- index */ |
| /* E0 -> */ 2, 3, /* <- F0 */ |
| 0, 0, |
| 0, 0, |
| 0, 0, |
| 0, 4, /* <- F4 */ |
| 0, 0, |
| 0, 0, |
| 0, 0, |
| 0, 0, |
| 0, 0, |
| 0, 0, |
| 0, 0, |
| 0, 0, |
| /* ED -> */ 3, 0, |
| 0, 0, |
| 0, 0, |
| }; |
| // clang-format on |
| const uint8x16x2_t range_adjust_tbl = vld2q_u8(range_adjust_tbl_data); |
| |
| const uint8x16_t const_1 = vdupq_n_u8(1); |
| const uint8x16_t const_2 = vdupq_n_u8(2); |
| const uint8x16_t const_e0 = vdupq_n_u8(0xE0); |
| |
| uint8x16_t prev_input = vdupq_n_u8(0); |
| uint8x16_t prev_first_len = vdupq_n_u8(0); |
| uint8x16_t error = vdupq_n_u8(0); |
| |
| while (end - data >= 16) { |
| const uint8x16_t input = vld1q_u8((const uint8_t*)data); |
| |
| const uint8x16_t high_nibbles = vshrq_n_u8(input, 4); |
| |
| const uint8x16_t first_len = vqtbl1q_u8(first_len_tbl, high_nibbles); |
| |
| uint8x16_t range = vqtbl1q_u8(first_range_tbl, high_nibbles); |
| |
| range = vorrq_u8(range, vextq_u8(prev_first_len, first_len, 15)); |
| |
| uint8x16_t shift2 = vextq_u8(prev_first_len, first_len, 14); |
| shift2 = vqsubq_u8(shift2, const_1); |
| range = vorrq_u8(range, shift2); |
| |
| uint8x16_t shift3 = vextq_u8(prev_first_len, first_len, 13); |
| shift3 = vqsubq_u8(shift3, const_2); |
| range = vorrq_u8(range, shift3); |
| |
| uint8x16_t shift1 = vextq_u8(prev_input, input, 15); |
| shift1 = vsubq_u8(shift1, const_e0); |
| range = vaddq_u8(range, vqtbl2q_u8(range_adjust_tbl, shift1)); |
| |
| const uint8x16_t min_range = vqtbl1q_u8(range_min_tbl, range); |
| const uint8x16_t max_range = vqtbl1q_u8(range_max_tbl, range); |
| |
| if (return_position) { |
| error = vcltq_u8(input, min_range); |
| error = vorrq_u8(error, vcgtq_u8(input, max_range)); |
| if (vmaxvq_u32(vreinterpretq_u32_u8(error))) { |
| break; |
| } |
| } else { |
| error = vorrq_u8(error, vcltq_u8(input, min_range)); |
| error = vorrq_u8(error, vcgtq_u8(input, max_range)); |
| } |
| |
| prev_input = input; |
| prev_first_len = first_len; |
| |
| data += 16; |
| } |
| |
| if (return_position && data == data_original) { |
| return utf8_range_ValidateUTF8Naive(data, end, return_position); |
| } |
| const int32_t prev = vgetq_lane_s32(vreinterpretq_s32_u8(prev_input), 3); |
| data -= utf8_range_CodepointSkipBackwards(prev); |
| if (return_position) { |
| return (data - data_original) + |
| utf8_range_ValidateUTF8Naive(data, end, return_position); |
| } |
| if (vmaxvq_u32(vreinterpretq_u32_u8(error))) { |
| return 0; |
| } |
| return utf8_range_ValidateUTF8Naive(data, end, return_position); |
| } |