DAW JSON Link
Loading...
Searching...
No Matches
daw_not_const_ex_functions.h
Go to the documentation of this file.
1// Copyright (c) Darrell Wright
2//
3// Distributed under the Boost Software License, Version 1.0. (See accompanying
4// file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
5//
6// Official repository: https://github.com/beached/daw_json_link
7//
8
9#pragma once
10
12
16
17#include <daw/daw_attributes.h>
18#include <daw/daw_cpp_feature_check.h>
19#include <daw/daw_cxmath.h>
20#include <daw/daw_do_n.h>
21#include <daw/daw_likely.h>
22#include <daw/daw_logic.h>
23#include <daw/daw_not_null.h>
24#include <daw/daw_span.h>
25#include <daw/daw_uint_buffer.h>
26#include <daw/daw_unreachable.h>
27
28#if defined( DAW_HAS_MSVC_LIKE )
29#include <intrin.h>
30#endif
31
32#include <cstddef>
33#include <cstring>
34#include <type_traits>
35
36namespace daw::json {
37 inline namespace DAW_JSON_VER {
38 namespace json_details {
39 DAW_ATTRIB_INLINE
40 constexpr bool is_escaped( daw::not_null<char const *> ptr,
41 daw::not_null<char const *> min_ptr ) {
42 if( *( ptr - 1 ) != '\\' ) {
43 return false;
44 }
45 if( ( ptr - min_ptr ) < 2 ) {
46 return false;
47 }
48 return *( ptr - 2 ) != '\\';
49 }
50
51#if defined( DAW_JSON_HAS_SIMD )
52#if not defined( DAW_HAS_MSVC_LIKE )
53 constexpr
54#else
55 inline
56#endif
57 std::ptrdiff_t find_lsb_set( std::uint64_t value ) {
58#if DAW_HAS_BUILTIN( __builtin_ffsll )
59 return __builtin_ffsll( static_cast<long long>( value ) ) - 1;
60#elif defined( DAW_HAS_MSVC_LIKE )
61 if( not DAW_IS_CONSTANT_EVALUATED( ) ) {
62 unsigned long index;
63#if defined( _M_X64 ) or defined( _M_ARM64 )
64 if( _BitScanForward64( &index, value ) != 0 ) {
65 return static_cast<std::ptrdiff_t>( index );
66 }
67#else
68 auto const low = static_cast<unsigned long>( value );
69 if( _BitScanForward( &index, low ) != 0 ) {
70 return static_cast<std::ptrdiff_t>( index );
71 }
72 auto const high = static_cast<unsigned long>( value >> 32U );
73 if( _BitScanForward( &index, high ) != 0 ) {
74 return static_cast<std::ptrdiff_t>( index ) + 32;
75 }
76#endif
77 return -1;
78 }
79#endif
80 std::ptrdiff_t result = 0;
81 if( value == 0 ) {
82 return -1;
83 }
84 while( ( value & 1 ) == 0 ) {
85 value >>= 1;
86 ++result;
87 }
88 return result;
89 }
90
91 using char_simd_t = daw::simd::vec<char>;
92 static constexpr std::size_t char_simd_size = char_simd_t::size( );
93 static_assert( char_simd_size <= 64 and
94 ( char_simd_size & ( char_simd_size - 1U ) ) == 0,
95 "Block size must be a power of two no larger than 64" );
96
102 [[nodiscard]] DAW_ATTRIB_INLINE std::uintptr_t
103 align_down_to_block( std::uintptr_t addr ) {
104 return addr & ~std::uintptr_t{ char_simd_size - 1U };
105 }
106
107 template<char k>
108 DAW_ATTRIB_INLINE std::uint64_t mem_find_eq_simd( char_simd_t block ) {
109 return ( block == char_simd_t( k ) ).to_ullong( );
110 }
111
112 template<bool is_unchecked_input, char... keys>
113 DAW_ATTRIB_INLINE daw::not_null<char const *>
114 mem_move_to_next_of_simd( daw::not_null<char const *> first,
115 daw::not_null<char const *> const last ) {
116 if( first >= last ) {
117 return last;
118 }
119 auto const first_addr = reinterpret_cast<std::uintptr_t>( first.get( ) );
120 auto const last_addr = reinterpret_cast<std::uintptr_t>( last.get( ) );
121 auto const find_keys = []( char_simd_t block ) {
122 return ( mem_find_eq_simd<keys>( block ) | ... );
123 };
124 std::uintptr_t block_addr;
125 std::uint64_t key_positions;
126 if( last_addr - first_addr >= char_simd_size ) {
127 // Most strings are short, one unaligned load usually covers them
128 auto const key_positions0 =
129 find_keys( daw::simd::unchecked_load<char_simd_t>(
130 daw::span( first.get( ), char_simd_size ) ) );
131 if( key_positions0 != 0 ) {
132 return first + find_lsb_set( key_positions0 );
133 }
134 block_addr = align_down_to_block( first_addr + char_simd_size );
135 key_positions = ~std::uint64_t{ 0 }
136 << ( first_addr + char_simd_size - block_addr );
137 } else {
138 block_addr = align_down_to_block( first_addr );
139 key_positions = ~std::uint64_t{ 0 } << ( first_addr - block_addr );
140 }
141 if( block_addr >= last_addr ) {
142 return last;
143 }
144 key_positions &= find_keys(
145 load_aligned_block<char_simd_t>( block_addr, first_addr, last_addr ) );
146 while( key_positions == 0 ) {
147 block_addr += char_simd_size;
148 if( block_addr >= last_addr ) {
149 return last;
150 }
151 key_positions = find_keys(
152 load_aligned_block<char_simd_t>( block_addr, first_addr, last_addr ) );
153 }
154 auto const hit_addr =
155 block_addr +
156 static_cast<std::uintptr_t>( find_lsb_set( key_positions ) );
157 if( hit_addr >= last_addr ) {
158 return last;
159 }
160 return first + static_cast<std::ptrdiff_t>( hit_addr - first_addr );
161 }
162
163 // Adapted from
164 // https://github.com/simdjson/simdjson/blob/master/src/generic/stage1/json_string_scanner.h#L79
165 DAW_ATTRIB_INLINE constexpr std::uint64_t
166 find_escaped_branchless( std::uint64_t &prev_escaped,
167 std::uint64_t backslashes ) {
168 DAW_CPP23_STATIC_LOCAL constexpr std::uint64_t odd_bits =
169 0xAAAA'AAAA'AAAA'AAAAULL;
170 DAW_CPP23_STATIC_LOCAL constexpr auto valid_bits = [] {
171 if constexpr( char_simd_size == 64 ) {
172 return ~std::uint64_t{ 0 };
173 } else {
174 return ( std::uint64_t{ 1 } << char_simd_size ) - 1;
175 }
176 }( );
177 auto const potential_escape = backslashes & ~prev_escaped;
178 auto const maybe_escaped = potential_escape << 1U;
179 auto const escape_and_terminal =
180 ( ( maybe_escaped | odd_bits ) - potential_escape ) ^ odd_bits;
181 auto const escaped =
182 ( escape_and_terminal ^ ( backslashes | prev_escaped ) ) & valid_bits;
183 auto const escape_bits = escape_and_terminal & backslashes;
184 prev_escaped = ( escape_bits >> ( char_simd_size - 1U ) ) & 1U;
185 return escaped;
186 }
187
195 template<bool is_unchecked_input>
196 inline daw::not_null<char const *>
197 mem_skip_until_end_of_string_simd( daw::not_null<char const *> first,
198 daw::not_null<char const *> const last,
199 std::ptrdiff_t *first_escape ) {
200 if( first >= last ) {
201 return last;
202 }
203 auto const first_addr = reinterpret_cast<std::uintptr_t>( first.get( ) );
204 auto const last_addr = reinterpret_cast<std::uintptr_t>( last.get( ) );
205 std::uint64_t prev_escapes = 0;
206 char const *result = nullptr;
207 // Scan one block. Bits below lead_shift are not part of the string
208 // or were already scanned. Returns true when the scan is done
209 auto const scan = [&]( char_simd_t block, std::uintptr_t block_addr,
210 unsigned lead_shift ) {
211 auto const lead_mask = ~std::uint64_t{ 0 } << lead_shift;
212 auto const backslashes = mem_find_eq_simd<'\\'>( block ) & lead_mask;
213 prev_escapes <<= lead_shift;
214 auto const escaped =
215 find_escaped_branchless( prev_escapes, backslashes );
216 auto const quotes =
217 mem_find_eq_simd<'"'>( block ) & lead_mask & ~escaped;
218 auto relevant_backslashes = backslashes;
219 if( quotes != 0 ) {
220 auto const quote_pos = find_lsb_set( quotes );
221 relevant_backslashes &=
222 ( std::uint64_t{ 1 } << static_cast<unsigned>( quote_pos ) ) - 1U;
223 }
224 if( first_escape != nullptr and *first_escape < 0 and
225 relevant_backslashes != 0 ) {
226 auto const escape_addr =
227 block_addr + static_cast<std::uintptr_t>(
228 find_lsb_set( relevant_backslashes ) );
229 if( escape_addr < last_addr ) {
230 *first_escape =
231 static_cast<std::ptrdiff_t>( escape_addr - first_addr );
232 }
233 }
234 if( quotes == 0 ) {
235 return false;
236 }
237 auto const quote_addr =
238 block_addr + static_cast<std::uintptr_t>( find_lsb_set( quotes ) );
239 result = quote_addr >= last_addr
240 ? last.get( )
241 : first.get( ) +
242 static_cast<std::ptrdiff_t>( quote_addr - first_addr );
243 return true;
244 };
245 std::uintptr_t block_addr;
246 unsigned lead_shift;
247 if( last_addr - first_addr >= char_simd_size ) {
248 if( scan( daw::simd::unchecked_load<char_simd_t>(
249 daw::span( first.get( ), char_simd_size ) ),
250 first_addr, 0 ) ) {
251 return result;
252 }
253 block_addr = align_down_to_block( first_addr + char_simd_size );
254 lead_shift = static_cast<unsigned>( first_addr + char_simd_size -
255 block_addr );
256 } else {
257 block_addr = align_down_to_block( first_addr );
258 lead_shift = static_cast<unsigned>( first_addr - block_addr );
259 }
260 while( block_addr < last_addr ) {
261 if( scan(
262 load_aligned_block<char_simd_t>( block_addr, first_addr, last_addr ),
263 block_addr, lead_shift ) ) {
264 return result;
265 }
266 block_addr += char_simd_size;
267 lead_shift = 0;
268 }
269 return last;
270 }
271
272 template<bool is_unchecked_input>
273 inline daw::not_null<char const *>
274 mem_skip_until_end_of_string_simd( daw::not_null<char const *> first,
275 daw::not_null<char const *> const last ) {
276 return mem_skip_until_end_of_string_simd<is_unchecked_input>(
277 first, last, nullptr );
278 }
279
280 template<bool is_unchecked_input>
281 inline daw::not_null<char const *>
282 mem_skip_until_end_of_string_simd( daw::not_null<char const *> first,
283 daw::not_null<char const *> const last,
284 std::ptrdiff_t &first_escape ) {
285 return mem_skip_until_end_of_string_simd<is_unchecked_input>(
286 first, last, &first_escape );
287 }
288
289#endif
290 template<bool is_unchecked_input, char... keys>
291 DAW_ATTRIB_INLINE daw::not_null<char const *>
292 mem_move_to_next_of_runtime( daw::not_null<char const *> first,
293 daw::not_null<char const *> last ) {
294 if constexpr( sizeof...( keys ) == 1 ) {
295 char const key[]{ keys... };
296 char const *ptr = static_cast<char const *>( std::memchr(
297 first, key[0], static_cast<std::size_t>( last - first ) ) );
298 if( ptr == nullptr ) {
299 ptr = last;
300 }
301 return ptr;
302 } else {
303 DAW_CPP23_STATIC_LOCAL constexpr auto eq =
304 []( char l, char r ) DAW_JSON_CPP23_STATIC_CALL_OP {
305 return l == r;
306 };
307 while( is_unchecked_input or first < last ) {
308 char const c = *first;
309 if( nsc_or( eq( c, keys )... ) ) {
310 return first;
311 }
312 ++first;
313 }
314 return first;
315 }
316 }
317 template<bool is_unchecked_input, typename ExecTag, char... keys>
318 DAW_ATTRIB_FLATTEN daw::not_null<char const *>
319 mem_move_to_next_of( daw::not_null<char const *> first,
320 daw::not_null<char const *> last ) {
321
322#if defined( DAW_JSON_HAS_SIMD )
323 if( use_simd_exec_mode<ExecTag>( ) ) {
324 return mem_move_to_next_of_simd<is_unchecked_input, keys...>( first,
325 last );
326 }
327#endif
328 return mem_move_to_next_of_runtime<is_unchecked_input, keys...>( first,
329 last );
330 }
331
332 template<bool is_unchecked_input>
333 DAW_ATTRIB_INLINE daw::not_null<char const *>
334 mem_skip_until_end_of_string_runtime(
335 daw::not_null<char const *> first,
336 daw::not_null<char const *> const last ) {
337 if constexpr( not is_unchecked_input ) {
338 daw_json_ensure( first < last, ErrorReason::UnexpectedEndOfData );
339 }
340 first =
341 mem_move_to_next_of<is_unchecked_input, runtime_exec_tag, '\\', '"'>(
342 first, last );
343 while( is_unchecked_input or first < last ) {
344 switch( *first ) {
345 case '"':
346 return first;
347 case '\\':
348 ++first;
349 break;
350 }
351 ++first;
352 first = mem_move_to_next_of<is_unchecked_input,
354 '\\',
355 '"'>( first, last );
356 }
357 return first;
358 }
359
360 template<bool is_unchecked_input>
361 DAW_ATTRIB_INLINE constexpr daw::not_null<char const *>
362 mem_skip_until_end_of_string_constexpr(
363 daw::not_null<char const *> first,
364 daw::not_null<char const *> const last ) {
365 if( first == last ) {
366 return first;
367 }
368 using char_t = std::remove_const_t<char const>;
369 // Check if the last valid char is a '\'. If not we can skip a check
370 // in the loop on escaped things
371 if( is_unchecked_input or
372 DAW_LIKELY( *( last - 1 ) != char_t{ '\\' } ) ) {
373 while( is_unchecked_input or DAW_UNLIKELY( first < last ) ) {
374 char const c = *first;
375 if( c == char_t{ '"' } ) {
376 break;
377 }
378 if( c == char_t{ '\\' } ) {
379 // We know that the last \ character is not the last character
380 // in range
381 first += 2;
382 } else {
383 ++first;
384 }
385 }
386 } else {
387 while( is_unchecked_input or DAW_UNLIKELY( first < last ) ) {
388 char const c = *first;
389 if( c == char_t{ '"' } ) {
390 break;
391 }
392 if( c == char_t{ '\\' } ) {
393 if( DAW_LIKELY( first + 1 < last ) ) {
394 first += 2;
395 } else {
396 first = last;
397 break;
398 }
399 } else {
400 ++first;
401 }
402 }
403 }
404 return first;
405 }
406
407 template<bool is_unchecked_input, typename ExecTag>
408 DAW_ATTRIB_INLINE
409 daw::not_null<char const *> constexpr mem_skip_until_end_of_string(
410 daw::not_null<char const *> first,
411 daw::not_null<char const *> const last ) {
412 if( use_constexpr_exec_mode<ExecTag>( ) ) {
413 return mem_skip_until_end_of_string_constexpr<is_unchecked_input>(
414 first, last );
415 }
416#if defined( DAW_JSON_HAS_SIMD )
417 if( use_simd_exec_mode<ExecTag>( ) ) {
418 return mem_skip_until_end_of_string_simd<is_unchecked_input>( first,
419 last );
420 }
421#endif
422 return mem_skip_until_end_of_string_runtime<is_unchecked_input>( first,
423 last );
424 }
425
426 template<bool is_unchecked_input>
427 DAW_ATTRIB_INLINE daw::not_null<char const *>
428 mem_skip_until_end_of_string_runtime(
429 daw::not_null<char const *> first,
430 daw::not_null<char const *> const last, std::ptrdiff_t &first_escape ) {
431 auto first_first = first;
432 if constexpr( not is_unchecked_input ) {
433 daw_json_ensure( first < last, ErrorReason::UnexpectedEndOfData );
434 }
435 first =
436 mem_move_to_next_of<is_unchecked_input, runtime_exec_tag, '\\', '"'>(
437 first, last );
438 while( is_unchecked_input or first < last ) {
439 switch( *first ) {
440 case '"':
441 return first;
442 case '\\':
443 if( first_escape < 0 ) {
444 first_escape = first - first_first;
445 }
446 if constexpr( is_unchecked_input ) {
447 ++first;
448 } else {
449 first += static_cast<int>( static_cast<bool>( last - first ) );
450 }
451 break;
452 }
453 ++first;
454 first = mem_move_to_next_of<is_unchecked_input,
456 '\\',
457 '"'>( first, last );
458 }
459 return first;
460 }
461
462 template<bool is_unchecked_input, typename ExecTag>
463 DAW_ATTRIB_FLATINLINE inline daw::not_null<char const *>
464 mem_skip_until_end_of_string( daw::not_null<char const *> first,
465 daw::not_null<char const *> const last,
466 std::ptrdiff_t &first_escape ) {
467#if defined( DAW_JSON_HAS_SIMD )
468 if( use_simd_exec_mode<ExecTag>( ) ) {
469 return mem_skip_until_end_of_string_simd<is_unchecked_input>(
470 first, last, first_escape );
471 }
472#endif
473 return mem_skip_until_end_of_string_runtime<is_unchecked_input>(
474 first, last, first_escape );
475 }
476 } // namespace json_details
477 } // namespace DAW_JSON_VER
478} // namespace daw::json
#define daw_json_ensure(Bool,...)
Ensure that Bool is true. If false pass rest of args to daw_json_error.
#define DAW_JSON_CPP23_STATIC_CALL_OP
string_writer grows its string with resize_and_overwrite, leaving the new bytes uninitialized,...
#define DAW_JSON_VER
The version string used in namespace definitions. Must be a valid namespace name.
Definition version.h:20