2828#include < string>
2929#include < vector>
3030
31+ #ifdef ENABLE_EXPERIMENTAL_SIMD_SUPPORT_NEON
32+ #if defined(__ARM_NEON)
33+ #include < arm_neon.h>
34+
35+ #include < cstdint>
36+ #else
37+ #error ARM NEON support required
38+ #endif
39+ #endif
40+
3141namespace McBopomofo {
3242
43+ namespace {
44+
45+ #ifdef ENABLE_EXPERIMENTAL_SIMD_SUPPORT_NEON
46+
47+ int FirstNonZeroLane16 (uint8x16_t value) {
48+ // value must be a comparison mask whose lanes are either 0x00 or 0xff.
49+ // Taking the maximum across the reversed masked lane indices locates the
50+ // first matching lane and avoids a scalar loop.
51+ alignas (16 ) static constexpr uint8_t kReverseLaneIndices [16 ] = {
52+ 16 , 15 , 14 , 13 , 12 , 11 , 10 , 9 , 8 , 7 , 6 , 5 , 4 , 3 , 2 , 1 ,
53+ };
54+ const uint8x16_t laneIndices = vld1q_u8 (kReverseLaneIndices );
55+ return 16 - static_cast <int >(vmaxvq_u8 (vandq_u8 (value, laneIndices)));
56+ }
57+
58+ int LastNonZeroLane16 (uint8x16_t value) {
59+ // value must be a comparison mask whose lanes are either 0x00 or 0xff.
60+ // Taking the maximum across the masked lane indices locates the last matching
61+ // lane and avoids a scalar loop that compilers may expand into up to 16 umov
62+ // and cbnz branch pairs.
63+ alignas (16 ) static constexpr uint8_t kLaneIndices [16 ] = {
64+ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 , 14 , 15 , 16 ,
65+ };
66+ const uint8x16_t laneIndices = vld1q_u8 (kLaneIndices );
67+ return static_cast <int >(vmaxvq_u8 (vandq_u8 (value, laneIndices))) - 1 ;
68+ }
69+
70+ #endif
71+
72+ const char * FindNextCharacter (const char * position, const char * end,
73+ char character) {
74+ const char * cursor = position;
75+
76+ #ifdef ENABLE_EXPERIMENTAL_SIMD_SUPPORT_NEON
77+ const uint8x16_t characters = vdupq_n_u8 (static_cast <uint8_t >(character));
78+ while (end - cursor >= 16 ) {
79+ const uint8x16_t block = vld1q_u8 (reinterpret_cast <const uint8_t *>(cursor));
80+ const int positionInBlock = FirstNonZeroLane16 (vceqq_u8 (block, characters));
81+ if (positionInBlock != 16 ) {
82+ return cursor + positionInBlock;
83+ }
84+ cursor += 16 ;
85+ }
86+ #endif
87+
88+ while (cursor != end && *cursor != character) {
89+ ++cursor;
90+ }
91+ return cursor;
92+ }
93+
94+ const char * FindLineStart (const char * begin, const char * position) {
95+ const char * cursor = position;
96+
97+ #ifdef ENABLE_EXPERIMENTAL_SIMD_SUPPORT_NEON
98+ const uint8x16_t linefeeds = vdupq_n_u8 (static_cast <uint8_t >(' \n ' ));
99+ while (cursor - begin >= 16 ) {
100+ const char * blockStart = cursor - 16 ;
101+ const uint8x16_t block =
102+ vld1q_u8 (reinterpret_cast <const uint8_t *>(blockStart));
103+ const int positionInBlock = LastNonZeroLane16 (vceqq_u8 (block, linefeeds));
104+ if (positionInBlock >= 0 ) {
105+ return blockStart + positionInBlock + 1 ;
106+ }
107+ cursor = blockStart;
108+ }
109+ #endif
110+
111+ while (cursor != begin) {
112+ --cursor;
113+ if (*cursor == ' \n ' ) {
114+ return cursor + 1 ;
115+ }
116+ }
117+ return begin;
118+ }
119+
120+ } // namespace
121+
33122bool ParselessPhraseDB::ValidatePragma (const char * buf, size_t length) {
34123 if (length < SORTED_PRAGMA_HEADER .length ()) {
35124 return false ;
@@ -80,11 +169,7 @@ std::vector<std::string_view> ParselessPhraseDB::findRows(
80169
81170 while (ptr + key.length () <= end_ &&
82171 memcmp (ptr, key.data (), key.length ()) == 0 ) {
83- const char * eol = ptr;
84-
85- while (eol != end_ && *eol != ' \n ' ) {
86- ++eol;
87- }
172+ const char * eol = FindNextCharacter (ptr, end_, ' \n ' );
88173
89174 rows.emplace_back (ptr, eol - ptr);
90175 if (eol == end_) {
@@ -114,20 +199,11 @@ const char* ParselessPhraseDB::findFirstMatchingLine(
114199
115200 while (top < bottom) {
116201 const char * mid = top + ((bottom - top) / 2 );
117- const char * ptr = mid;
118-
119- if (ptr != begin_) {
120- --ptr;
121- }
122-
123- while (ptr != begin_ && *ptr != ' \n ' ) {
124- --ptr;
125- }
202+ const char * ptr = FindLineStart (begin_, mid);
126203
127204 const char * prev = nullptr ;
128- if (*ptr == ' \n ' ) {
129- prev = ptr;
130- ++ptr;
205+ if (ptr != begin_) {
206+ prev = ptr - 1 ;
131207 }
132208
133209 // ptr is now in the "current" line we're interested in.
@@ -153,15 +229,7 @@ const char* ParselessPhraseDB::findFirstMatchingLine(
153229 }
154230
155231 // Move the prev so that it reaches the previous line.
156- if (prev != begin_) {
157- --prev;
158- }
159- while (prev != begin_ && *prev != ' \n ' ) {
160- --prev;
161- }
162- if (*prev == ' \n ' ) {
163- ++prev;
164- }
232+ prev = FindLineStart (begin_, prev);
165233
166234 int prev_cmp = memcmp (prev, key.data (), key.length ());
167235
@@ -187,20 +255,15 @@ std::vector<std::string> ParselessPhraseDB::reverseFindRows(
187255 const char * ptr = recordBegin;
188256
189257 // skip over the key to find the field separator
190- while (ptr < end_ && *ptr != ' ' ) {
191- ++ptr;
192- }
258+ ptr = FindNextCharacter (ptr, end_, ' ' );
193259 // skip over the field separator. there should be just one, but loop just in
194260 // case.
195261 while (ptr < end_ && *ptr == ' ' ) {
196262 ++ptr;
197263 }
198264
199265 // now walk to the end of this record
200- const char * recordEnd = ptr;
201- while (recordEnd < end_ && *recordEnd != ' \n ' ) {
202- ++recordEnd;
203- }
266+ const char * recordEnd = FindNextCharacter (ptr, end_, ' \n ' );
204267
205268 if (ptr + value.length () < end_ &&
206269 memcmp (ptr, value.data (), value.length ()) == 0 ) {
0 commit comments