From adaef43bc6b2afd112311e5b2a00145fc1fda58c Mon Sep 17 00:00:00 2001 From: John Keiser Date: Wed, 22 Jan 2020 11:11:14 -0800 Subject: [PATCH] Find all escaped characters with simpler algorithm (#450) --- src/arm64/simd.h | 6 +- src/generic/stage1_find_marks.h | 98 ++++++++++++++++++++------------- 2 files changed, 64 insertions(+), 40 deletions(-) diff --git a/src/arm64/simd.h b/src/arm64/simd.h index 0584e0313..b0a7a51e8 100644 --- a/src/arm64/simd.h +++ b/src/arm64/simd.h @@ -107,7 +107,7 @@ namespace simdjson::arm64::simd { } // Store to array - really_inline void store(uint8_t dst[16]) { return vst1q_u8(dst, *this); } + really_inline void store(uint8_t dst[16]) const { return vst1q_u8(dst, *this); } // Saturated math really_inline simd8 saturating_add(const simd8 other) const { return vqaddq_u8(*this, other); } @@ -207,7 +207,7 @@ namespace simdjson::arm64::simd { } // Store to array - really_inline void store(int8_t dst[16]) { return vst1q_s8(dst, *this); } + really_inline void store(int8_t dst[16]) const { return vst1q_s8(dst, *this); } // Explicit conversion to/from unsigned really_inline explicit simd8(const uint8x16_t other): simd8(vreinterpretq_s8_u8(other)) {} @@ -265,7 +265,7 @@ namespace simdjson::arm64::simd { really_inline simd8x64(const simd8 chunk0, const simd8 chunk1, const simd8 chunk2, const simd8 chunk3) : chunks{chunk0, chunk1, chunk2, chunk3} {} really_inline simd8x64(const T ptr[64]) : chunks{simd8::load(ptr), simd8::load(ptr+16), simd8::load(ptr+32), simd8::load(ptr+48)} {} - really_inline void store(T ptr[64]) { + really_inline void store(T ptr[64]) const { this->chunks[0].store(ptr+sizeof(simd8)*0); this->chunks[1].store(ptr+sizeof(simd8)*1); this->chunks[2].store(ptr+sizeof(simd8)*2); diff --git a/src/generic/stage1_find_marks.h b/src/generic/stage1_find_marks.h index a08784200..ac95c2950 100644 --- a/src/generic/stage1_find_marks.h +++ b/src/generic/stage1_find_marks.h @@ -123,44 +123,68 @@ public: really_inline void scan(const uint8_t *buf, const size_t len, utf8_checker &utf8_checker); }; -// return a bitvector indicating where we have characters that end an odd-length -// sequence of backslashes (and thus change the behavior of the next character -// to follow). A even-length sequence of backslashes, and, for that matter, the -// largest even-length prefix of our odd-length sequence of backslashes, simply -// modify the behavior of the backslashes themselves. -// We also update the prev_iter_ends_odd_backslash reference parameter to -// indicate whether we end an iteration on an odd-length sequence of -// backslashes, which modifies our subsequent search for odd-length -// sequences of backslashes in an obvious way. -really_inline uint64_t follows_odd_sequence_of(const uint64_t match, uint64_t &overflow) { +// Routines to print masks and text for debugging bitmask operations +UNUSED static char * format_input_text(const simd8x64 in) { + static char *buf = (char*)malloc(sizeof(simd8x64) + 1); + in.store((uint8_t*)buf); + for (size_t i=0; i); i++) { + if (buf[i] < ' ') { buf[i] = '_'; } + } + buf[sizeof(simd8x64)] = '\0'; + return buf; +} + +UNUSED static char * format_mask(uint64_t mask) { + static char *buf = (char*)malloc(64 + 1); + for (size_t i=0; i<64; i++) { + buf[i] = (mask & (size_t(1) << i)) ? 'X' : ' '; + } + buf[64] = '\0'; + return buf; +} + +// +// Finds escaped characters (characters following \). +// +// Handles runs of backslashes like \\\" and \\\\" correctly (yielding 0101 and 01010, respectively). +// +// Does this by: +// - Shift the escape mask to get potentially escaped characters (characters after backslashes). +// - Mask escaped sequences that start on *even* bits with 1010101010 (odd bits are escaped, even bits are not) +// - Mask escaped sequences that start on *odd* bits with 0101010101 (even bits are escaped, odd bits are not) +// +// To distinguish between escaped sequences starting on even/odd bits, it finds the start of all +// escape sequences, filters out the ones that start on even bits, and adds that to the mask of +// escape sequences. This causes the addition to clear out the sequences starting on odd bits (since +// the start bit causes a carry), and leaves even-bit sequences alone. +// +// Example: +// +// text | \\\ | \\\"\\\" \\\" \\"\\" | +// escape | xxx | xx xxx xxx xx xx | Removed overflow backslash; will | it into follows_escape +// odd_starts | x | x x x | escape & ~even_bits & ~follows_escape +// even_seq | c| cxxx c xx c | c = carry bit -- will be masked out later +// invert_mask | | cxxx c xx c| even_seq << 1 +// follows_escape | xx | x xx xxx xxx xx xx | Includes overflow bit +// escaped | x | x x x x x x x x | +// desired | x | x x x x x x x x | +// text | \\\ | \\\"\\\" \\\" \\"\\" | +// +really_inline uint64_t find_escaped(uint64_t escape, uint64_t &escaped_overflow) { + // If there was overflow, pretend the first character isn't a backslash + escape &= ~escaped_overflow; + uint64_t follows_escape = escape << 1 | escaped_overflow; + + // Get sequences starting on even bits by clearing out the odd series using + const uint64_t even_bits = 0x5555555555555555ULL; - const uint64_t odd_bits = ~even_bits; - uint64_t start_edges = match & ~(match << 1); - /* flip lowest if we have an odd-length run at the end of the prior - * iteration */ - uint64_t even_start_mask = even_bits ^ overflow; - uint64_t even_starts = start_edges & even_start_mask; - uint64_t odd_starts = start_edges & ~even_start_mask; - uint64_t even_carries = match + even_starts; + uint64_t odd_sequence_starts = escape & ~even_bits & ~follows_escape; + uint64_t sequences_starting_on_even_bits; + escaped_overflow = add_overflow(odd_sequence_starts, escape, &sequences_starting_on_even_bits); + uint64_t invert_mask = sequences_starting_on_even_bits << 1; // The mask we want to return is the *escaped* bits, not escapes. - uint64_t odd_carries; - /* must record the carry-out of our odd-carries out of bit 63; this - * indicates whether the sense of any edge going to the next iteration - * should be flipped */ - bool new_overflow = add_overflow(match, odd_starts, &odd_carries); - - odd_carries |= overflow; /* push in bit zero as a - * potential end if we had an - * odd-numbered run at the - * end of the previous - * iteration */ - overflow = new_overflow ? 0x1ULL : 0x0ULL; - uint64_t even_carry_ends = even_carries & ~match; - uint64_t odd_carry_ends = odd_carries & ~match; - uint64_t even_start_odd_end = even_carry_ends & odd_bits; - uint64_t odd_start_even_end = odd_carry_ends & even_bits; - uint64_t odd_ends = even_start_odd_end | odd_start_even_end; - return odd_ends; + // Mask every other backslashed character as an escaped character + // Flip the mask for sequences that start on even bits, to correct them + return (even_bits ^ invert_mask) & follows_escape; } // @@ -209,7 +233,7 @@ really_inline ErrorValues json_structural_scanner::detect_errors_on_eof(bool str // really_inline uint64_t json_structural_scanner::find_strings(const simd::simd8x64 in) { const uint64_t backslash = in.eq('\\'); - const uint64_t escaped = follows_odd_sequence_of(backslash, prev_escaped); + const uint64_t escaped = find_escaped(backslash, prev_escaped); const uint64_t quote = in.eq('"') & ~escaped; // prefix_xor flips on bits inside the string (and flips off the end quote). const uint64_t in_string = prefix_xor(quote) ^ prev_in_string;