Coverage Report

Created: 2026-08-19 06:33

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