Coverage Report

Created: 2026-10-16 07:06

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/snappy/snappy-internal.h
Line
Count
Source
1
// Copyright 2008 Google Inc. All Rights Reserved.
2
//
3
// Redistribution and use in source and binary forms, with or without
4
// modification, are permitted provided that the following conditions are
5
// met:
6
//
7
//     * Redistributions of source code must retain the above copyright
8
// notice, this list of conditions and the following disclaimer.
9
//     * Redistributions in binary form must reproduce the above
10
// copyright notice, this list of conditions and the following disclaimer
11
// in the documentation and/or other materials provided with the
12
// distribution.
13
//     * Neither the name of Google Inc. nor the names of its
14
// contributors may be used to endorse or promote products derived from
15
// this software without specific prior written permission.
16
//
17
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
//
29
// Internals shared between the Snappy implementation and its unittest.
30
31
#ifndef THIRD_PARTY_SNAPPY_SNAPPY_INTERNAL_H_
32
#define THIRD_PARTY_SNAPPY_SNAPPY_INTERNAL_H_
33
34
#include <utility>
35
36
#include "snappy-stubs-internal.h"
37
38
#if SNAPPY_HAVE_SSSE3
39
// Please do not replace with <x86intrin.h> or with headers that assume more
40
// advanced SSE versions without checking with all the OWNERS.
41
#include <emmintrin.h>
42
#include <tmmintrin.h>
43
#endif
44
45
#if SNAPPY_HAVE_NEON
46
#include <arm_neon.h>
47
#endif
48
49
#if SNAPPY_RVV_1 || SNAPPY_RVV_0_7
50
#define SNAPPY_HAVE_RVV 1
51
#include <riscv_vector.h>
52
#else
53
#define SNAPPY_HAVE_RVV 0
54
#endif
55
56
#if SNAPPY_RVV_1 || SNAPPY_RVV_0_7
57
#define VSETVL_E8M2 __riscv_vsetvl_e8m2
58
#define VLE8_V_U8M2 __riscv_vle8_v_u8m2
59
#define VSE8_V_U8M2 __riscv_vse8_v_u8m2
60
#endif
61
62
#if SNAPPY_HAVE_SSSE3 || SNAPPY_HAVE_NEON
63
#define SNAPPY_HAVE_VECTOR_BYTE_SHUFFLE 1
64
#else
65
#define SNAPPY_HAVE_VECTOR_BYTE_SHUFFLE 0
66
#endif
67
68
namespace snappy {
69
namespace internal {
70
71
#if SNAPPY_HAVE_VECTOR_BYTE_SHUFFLE
72
#if SNAPPY_HAVE_SSSE3
73
using V128 = __m128i;
74
#elif SNAPPY_HAVE_NEON
75
using V128 = uint8x16_t;
76
#endif
77
78
// Load 128 bits of integer data. `src` must be 16-byte aligned.
79
inline V128 V128_Load(const V128* src);
80
81
// Load 128 bits of integer data. `src` does not need to be aligned.
82
inline V128 V128_LoadU(const V128* src);
83
84
// Store 128 bits of integer data. `dst` does not need to be aligned.
85
inline void V128_StoreU(V128* dst, V128 val);
86
87
// Shuffle packed 8-bit integers using a shuffle mask.
88
// Each packed integer in the shuffle mask must be in [0,16).
89
inline V128 V128_Shuffle(V128 input, V128 shuffle_mask);
90
91
// Constructs V128 with 16 chars |c|.
92
inline V128 V128_DupChar(char c);
93
94
#if SNAPPY_HAVE_SSSE3
95
inline V128 V128_Load(const V128* src) { return _mm_load_si128(src); }
96
97
inline V128 V128_LoadU(const V128* src) { return _mm_loadu_si128(src); }
98
99
inline void V128_StoreU(V128* dst, V128 val) { _mm_storeu_si128(dst, val); }
100
101
inline V128 V128_Shuffle(V128 input, V128 shuffle_mask) {
102
  return _mm_shuffle_epi8(input, shuffle_mask);
103
}
104
105
inline V128 V128_DupChar(char c) { return _mm_set1_epi8(c); }
106
107
#elif SNAPPY_HAVE_NEON
108
inline V128 V128_Load(const V128* src) {
109
  return vld1q_u8(reinterpret_cast<const uint8_t*>(src));
110
}
111
112
inline V128 V128_LoadU(const V128* src) {
113
  return vld1q_u8(reinterpret_cast<const uint8_t*>(src));
114
}
115
116
inline void V128_StoreU(V128* dst, V128 val) {
117
  vst1q_u8(reinterpret_cast<uint8_t*>(dst), val);
118
}
119
120
inline V128 V128_Shuffle(V128 input, V128 shuffle_mask) {
121
  assert(vminvq_u8(shuffle_mask) >= 0 && vmaxvq_u8(shuffle_mask) <= 15);
122
  return vqtbl1q_u8(input, shuffle_mask);
123
}
124
125
inline V128 V128_DupChar(char c) { return vdupq_n_u8(c); }
126
127
128
#endif
129
#endif  // SNAPPY_HAVE_VECTOR_BYTE_SHUFFLE
130
131
// Working memory performs a single allocation to hold all scratch space
132
// required for compression.
133
class WorkingMemory {
134
 public:
135
  explicit WorkingMemory(size_t input_size);
136
137
  // Non-allocating: lays out the scratch space in the caller-provided
138
  // buffer, which must be at least RequiredSize(input_size) bytes, aligned
139
  // at least as strictly as uint16_t, and must outlive "*this".
140
  WorkingMemory(size_t input_size, char* buffer);
141
  ~WorkingMemory();
142
143
  // The buffer size required by the non-allocating constructor above.
144
  static size_t RequiredSize(size_t input_size);
145
146
  // Allocates and clears a hash table using memory in "*this",
147
  // stores the number of buckets in "*table_size" and returns a pointer to
148
  // the base of the hash table.
149
  uint16_t* GetHashTable(size_t fragment_size, int* table_size) const;
150
0
  char* GetScratchInput() const { return input_; }
151
9.47k
  char* GetScratchOutput() const { return output_; }
152
153
 private:
154
  char* mem_;        // the allocated memory, never nullptr
155
  size_t size_;      // the size of the allocated memory, never 0
156
  bool owns_mem_;
157
  uint16_t* table_;  // the pointer to the hashtable
158
  char* input_;      // the pointer to the input scratch buffer
159
  char* output_;     // the pointer to the output scratch buffer
160
161
  // No copying
162
  WorkingMemory(const WorkingMemory&);
163
  void operator=(const WorkingMemory&);
164
};
165
166
// Flat array compression that does not emit the "uncompressed length"
167
// prefix. Compresses "input" string to the "*op" buffer.
168
//
169
// REQUIRES: "input_length <= kBlockSize"
170
// REQUIRES: "op" points to an array of memory that is at least
171
// "MaxCompressedLength(input_length)" in size.
172
// REQUIRES: All elements in "table[0..table_size-1]" are initialized to zero.
173
// REQUIRES: "table_size" is a power of two
174
//
175
// Returns an "end" pointer into "op" buffer.
176
// "end - op" is the compressed size of "input".
177
char* CompressFragment(const char* input,
178
                       size_t input_length,
179
                       char* op,
180
                       uint16_t* table,
181
                       const int table_size);
182
183
// Find the largest n such that
184
//
185
//   s1[0,n-1] == s2[0,n-1]
186
//   and n <= (s2_limit - s2).
187
//
188
// Return make_pair(n, n < 8).
189
// Does not read *s2_limit or beyond.
190
// Does not read *(s1 + (s2_limit - s2)) or beyond.
191
// Requires that s2_limit >= s2.
192
//
193
// In addition populate *data with the next 5 bytes from the end of the match.
194
// This is only done if 8 bytes are available (s2_limit - s2 >= 8). The point is
195
// that on some arch's this can be done faster in this routine than subsequent
196
// loading from s2 + n.
197
//
198
// Separate implementation for 64-bit, little-endian cpus.
199
// riscv and little-endian cpu choose this routinue can be done faster too.
200
#if !SNAPPY_IS_BIG_ENDIAN && \
201
    (defined(__x86_64__) || defined(_M_X64) || defined(ARCH_PPC) || \
202
     defined(__aarch64__) || (defined(__riscv) && (__riscv_xlen == 64)))
203
static inline std::pair<size_t, bool> FindMatchLength(const char* s1,
204
                                                      const char* s2,
205
                                                      const char* s2_limit,
206
3.32M
                                                      uint64_t* data) {
207
3.32M
  assert(s2_limit >= s2);
208
3.32M
  size_t matched = 0;
209
210
  // This block isn't necessary for correctness; we could just start looping
211
  // immediately.  As an optimization though, it is useful.  It creates some not
212
  // uncommon code paths that determine, without extra effort, whether the match
213
  // length is less than 8.  In short, we are hoping to avoid a conditional
214
  // branch, and perhaps get better code layout from the C++ compiler.
215
3.32M
  if (SNAPPY_PREDICT_TRUE(s2 <= s2_limit - 16)) {
216
3.31M
    uint64_t a1 = UNALIGNED_LOAD64(s1);
217
3.31M
    uint64_t a2 = UNALIGNED_LOAD64(s2);
218
3.31M
    if (SNAPPY_PREDICT_TRUE(a1 != a2)) {
219
      // This code is critical for performance. The reason is that it determines
220
      // how much to advance `ip` (s2). This obviously depends on both the loads
221
      // from the `candidate` (s1) and `ip`. Furthermore the next `candidate`
222
      // depends on the advanced `ip` calculated here through a load, hash and
223
      // new candidate hash lookup (a lot of cycles). This makes s1 (ie.
224
      // `candidate`) the variable that limits throughput. This is the reason we
225
      // go through hoops to have this function update `data` for the next iter.
226
      // The straightforward code would use *data, given by
227
      //
228
      // *data = UNALIGNED_LOAD64(s2 + matched_bytes) (Latency of 5 cycles),
229
      //
230
      // as input for the hash table lookup to find next candidate. However
231
      // this forces the load on the data dependency chain of s1, because
232
      // matched_bytes directly depends on s1. However matched_bytes is 0..7, so
233
      // we can also calculate *data by
234
      //
235
      // *data = AlignRight(UNALIGNED_LOAD64(s2), UNALIGNED_LOAD64(s2 + 8),
236
      //                    matched_bytes);
237
      //
238
      // The loads do not depend on s1 anymore and are thus off the bottleneck.
239
      // The straightforward implementation on x86_64 would be to use
240
      //
241
      // shrd rax, rdx, cl  (cl being matched_bytes * 8)
242
      //
243
      // unfortunately shrd with a variable shift has a 4 cycle latency. So this
244
      // only wins 1 cycle. The BMI2 shrx instruction is a 1 cycle variable
245
      // shift instruction but can only shift 64 bits. If we focus on just
246
      // obtaining the least significant 4 bytes, we can obtain this by
247
      //
248
      // *data = ConditionalMove(matched_bytes < 4, UNALIGNED_LOAD64(s2),
249
      //     UNALIGNED_LOAD64(s2 + 4) >> ((matched_bytes & 3) * 8);
250
      //
251
      // Writen like above this is not a big win, the conditional move would be
252
      // a cmp followed by a cmov (2 cycles) followed by a shift (1 cycle).
253
      // However matched_bytes < 4 is equal to
254
      // static_cast<uint32_t>(xorval) != 0. Writen that way, the conditional
255
      // move (2 cycles) can execute in parallel with FindLSBSetNonZero64
256
      // (tzcnt), which takes 3 cycles.
257
2.67M
      uint64_t xorval = a1 ^ a2;
258
2.67M
      int shift = Bits::FindLSBSetNonZero64(xorval);
259
2.67M
      size_t matched_bytes = shift >> 3;
260
2.67M
      uint64_t a3 = UNALIGNED_LOAD64(s2 + 4);
261
#ifndef __x86_64__
262
      a2 = static_cast<uint32_t>(xorval) == 0 ? a3 : a2;
263
#else
264
      // Ideally this would just be
265
      //
266
      // a2 = static_cast<uint32_t>(xorval) == 0 ? a3 : a2;
267
      //
268
      // However clang correctly infers that the above statement participates on
269
      // a critical data dependency chain and thus, unfortunately, refuses to
270
      // use a conditional move (it's tuned to cut data dependencies). In this
271
      // case there is a longer parallel chain anyway AND this will be fairly
272
      // unpredictable.
273
2.67M
      asm("testl %k2, %k2\n\t"
274
2.67M
          "cmovzq %1, %0\n\t"
275
2.67M
          : "+r"(a2)
276
2.67M
          : "r"(a3), "r"(xorval)
277
2.67M
          : "cc");
278
2.67M
#endif
279
2.67M
      *data = a2 >> (shift & (3 * 8));
280
2.67M
      return std::pair<size_t, bool>(matched_bytes, true);
281
2.67M
    } else {
282
639k
      matched = 8;
283
639k
      s2 += 8;
284
639k
    }
285
3.31M
  }
286
640k
  SNAPPY_PREFETCH(s1 + 64);
287
640k
  SNAPPY_PREFETCH(s2 + 64);
288
289
  // Find out how long the match is. We loop over the data 64 bits at a
290
  // time until we find a 64-bit block that doesn't match; then we find
291
  // the first non-matching bit and use that to calculate the total
292
  // length of the match.
293
8.46M
  while (SNAPPY_PREDICT_TRUE(s2 <= s2_limit - 16)) {
294
8.46M
    uint64_t a1 = UNALIGNED_LOAD64(s1 + matched);
295
8.46M
    uint64_t a2 = UNALIGNED_LOAD64(s2);
296
8.46M
    if (a1 == a2) {
297
7.82M
      s2 += 8;
298
7.82M
      matched += 8;
299
7.82M
    } else {
300
638k
      uint64_t xorval = a1 ^ a2;
301
638k
      int shift = Bits::FindLSBSetNonZero64(xorval);
302
638k
      size_t matched_bytes = shift >> 3;
303
638k
      uint64_t a3 = UNALIGNED_LOAD64(s2 + 4);
304
#ifndef __x86_64__
305
      a2 = static_cast<uint32_t>(xorval) == 0 ? a3 : a2;
306
#else
307
638k
      asm("testl %k2, %k2\n\t"
308
638k
          "cmovzq %1, %0\n\t"
309
638k
          : "+r"(a2)
310
638k
          : "r"(a3), "r"(xorval)
311
638k
          : "cc");
312
638k
#endif
313
638k
      *data = a2 >> (shift & (3 * 8));
314
638k
      matched += matched_bytes;
315
638k
      assert(matched >= 8);
316
638k
      return std::pair<size_t, bool>(matched, false);
317
638k
    }
318
8.46M
  }
319
19.4k
  while (SNAPPY_PREDICT_TRUE(s2 < s2_limit)) {
320
18.2k
    if (s1[matched] == *s2) {
321
16.9k
      ++s2;
322
16.9k
      ++matched;
323
16.9k
    } else {
324
1.34k
      if (s2 <= s2_limit - 8) {
325
1.05k
        *data = UNALIGNED_LOAD64(s2);
326
1.05k
      }
327
1.34k
      return std::pair<size_t, bool>(matched, matched < 8);
328
1.34k
    }
329
18.2k
  }
330
1.21k
  return std::pair<size_t, bool>(matched, matched < 8);
331
2.56k
}
332
#else
333
static inline std::pair<size_t, bool> FindMatchLength(const char* s1,
334
                                                      const char* s2,
335
                                                      const char* s2_limit,
336
                                                      uint64_t* data) {
337
  // Implementation based on the x86-64 version, above.
338
  assert(s2_limit >= s2);
339
  int matched = 0;
340
341
  while (s2 <= s2_limit - 4 &&
342
         UNALIGNED_LOAD32(s2) == UNALIGNED_LOAD32(s1 + matched)) {
343
    s2 += 4;
344
    matched += 4;
345
  }
346
  if (LittleEndian::IsLittleEndian() && s2 <= s2_limit - 4) {
347
    uint32_t x = UNALIGNED_LOAD32(s2) ^ UNALIGNED_LOAD32(s1 + matched);
348
    int matching_bits = Bits::FindLSBSetNonZero(x);
349
    matched += matching_bits >> 3;
350
    s2 += matching_bits >> 3;
351
  } else {
352
    while ((s2 < s2_limit) && (s1[matched] == *s2)) {
353
      ++s2;
354
      ++matched;
355
    }
356
  }
357
  if (s2 <= s2_limit - 8) *data = LittleEndian::Load64(s2);
358
  return std::pair<size_t, bool>(matched, matched < 8);
359
}
360
#endif
361
362
static inline size_t FindMatchLengthPlain(const char* s1, const char* s2,
363
4.01M
                                          const char* s2_limit) {
364
  // Implementation based on the x86-64 version, above.
365
4.01M
  assert(s2_limit >= s2);
366
4.01M
  int matched = 0;
367
368
13.6M
  while (s2 <= s2_limit - 8 &&
369
13.6M
         UNALIGNED_LOAD64(s2) == UNALIGNED_LOAD64(s1 + matched)) {
370
9.61M
    s2 += 8;
371
9.61M
    matched += 8;
372
9.61M
  }
373
4.01M
  if (LittleEndian::IsLittleEndian() && s2 <= s2_limit - 8) {
374
4.00M
    uint64_t x = UNALIGNED_LOAD64(s2) ^ UNALIGNED_LOAD64(s1 + matched);
375
4.00M
    int matching_bits = Bits::FindLSBSetNonZero64(x);
376
4.00M
    matched += matching_bits >> 3;
377
4.00M
    s2 += matching_bits >> 3;
378
4.00M
  } else {
379
7.23k
    while ((s2 < s2_limit) && (s1[matched] == *s2)) {
380
5.47k
      ++s2;
381
5.47k
      ++matched;
382
5.47k
    }
383
1.75k
  }
384
4.01M
  return matched;
385
4.01M
}
386
387
// Lookup tables for decompression code.  Give --snappy_dump_decompression_table
388
// to the unit test to recompute char_table.
389
390
enum {
391
  LITERAL = 0,
392
  COPY_1_BYTE_OFFSET = 1,  // 3 bit length + 3 bits of offset in opcode
393
  COPY_2_BYTE_OFFSET = 2,
394
  COPY_4_BYTE_OFFSET = 3
395
};
396
static const int kMaximumTagLength = 5;  // COPY_4_BYTE_OFFSET plus the actual offset.
397
398
// Data stored per entry in lookup table:
399
//      Range   Bits-used       Description
400
//      ------------------------------------
401
//      1..64   0..7            Literal/copy length encoded in opcode byte
402
//      0..7    8..10           Copy offset encoded in opcode byte / 256
403
//      0..4    11..13          Extra bytes after opcode
404
//
405
// We use eight bits for the length even though 7 would have sufficed
406
// because of efficiency reasons:
407
//      (1) Extracting a byte is faster than a bit-field
408
//      (2) It properly aligns copy offset so we do not need a <<8
409
static constexpr uint16_t char_table[256] = {
410
    // clang-format off
411
  0x0001, 0x0804, 0x1001, 0x2001, 0x0002, 0x0805, 0x1002, 0x2002,
412
  0x0003, 0x0806, 0x1003, 0x2003, 0x0004, 0x0807, 0x1004, 0x2004,
413
  0x0005, 0x0808, 0x1005, 0x2005, 0x0006, 0x0809, 0x1006, 0x2006,
414
  0x0007, 0x080a, 0x1007, 0x2007, 0x0008, 0x080b, 0x1008, 0x2008,
415
  0x0009, 0x0904, 0x1009, 0x2009, 0x000a, 0x0905, 0x100a, 0x200a,
416
  0x000b, 0x0906, 0x100b, 0x200b, 0x000c, 0x0907, 0x100c, 0x200c,
417
  0x000d, 0x0908, 0x100d, 0x200d, 0x000e, 0x0909, 0x100e, 0x200e,
418
  0x000f, 0x090a, 0x100f, 0x200f, 0x0010, 0x090b, 0x1010, 0x2010,
419
  0x0011, 0x0a04, 0x1011, 0x2011, 0x0012, 0x0a05, 0x1012, 0x2012,
420
  0x0013, 0x0a06, 0x1013, 0x2013, 0x0014, 0x0a07, 0x1014, 0x2014,
421
  0x0015, 0x0a08, 0x1015, 0x2015, 0x0016, 0x0a09, 0x1016, 0x2016,
422
  0x0017, 0x0a0a, 0x1017, 0x2017, 0x0018, 0x0a0b, 0x1018, 0x2018,
423
  0x0019, 0x0b04, 0x1019, 0x2019, 0x001a, 0x0b05, 0x101a, 0x201a,
424
  0x001b, 0x0b06, 0x101b, 0x201b, 0x001c, 0x0b07, 0x101c, 0x201c,
425
  0x001d, 0x0b08, 0x101d, 0x201d, 0x001e, 0x0b09, 0x101e, 0x201e,
426
  0x001f, 0x0b0a, 0x101f, 0x201f, 0x0020, 0x0b0b, 0x1020, 0x2020,
427
  0x0021, 0x0c04, 0x1021, 0x2021, 0x0022, 0x0c05, 0x1022, 0x2022,
428
  0x0023, 0x0c06, 0x1023, 0x2023, 0x0024, 0x0c07, 0x1024, 0x2024,
429
  0x0025, 0x0c08, 0x1025, 0x2025, 0x0026, 0x0c09, 0x1026, 0x2026,
430
  0x0027, 0x0c0a, 0x1027, 0x2027, 0x0028, 0x0c0b, 0x1028, 0x2028,
431
  0x0029, 0x0d04, 0x1029, 0x2029, 0x002a, 0x0d05, 0x102a, 0x202a,
432
  0x002b, 0x0d06, 0x102b, 0x202b, 0x002c, 0x0d07, 0x102c, 0x202c,
433
  0x002d, 0x0d08, 0x102d, 0x202d, 0x002e, 0x0d09, 0x102e, 0x202e,
434
  0x002f, 0x0d0a, 0x102f, 0x202f, 0x0030, 0x0d0b, 0x1030, 0x2030,
435
  0x0031, 0x0e04, 0x1031, 0x2031, 0x0032, 0x0e05, 0x1032, 0x2032,
436
  0x0033, 0x0e06, 0x1033, 0x2033, 0x0034, 0x0e07, 0x1034, 0x2034,
437
  0x0035, 0x0e08, 0x1035, 0x2035, 0x0036, 0x0e09, 0x1036, 0x2036,
438
  0x0037, 0x0e0a, 0x1037, 0x2037, 0x0038, 0x0e0b, 0x1038, 0x2038,
439
  0x0039, 0x0f04, 0x1039, 0x2039, 0x003a, 0x0f05, 0x103a, 0x203a,
440
  0x003b, 0x0f06, 0x103b, 0x203b, 0x003c, 0x0f07, 0x103c, 0x203c,
441
  0x0801, 0x0f08, 0x103d, 0x203d, 0x1001, 0x0f09, 0x103e, 0x203e,
442
  0x1801, 0x0f0a, 0x103f, 0x203f, 0x2001, 0x0f0b, 0x1040, 0x2040,
443
    // clang-format on
444
};
445
446
}  // end namespace internal
447
}  // end namespace snappy
448
449
#endif  // THIRD_PARTY_SNAPPY_SNAPPY_INTERNAL_H_