avila-compress 0.8.0

Native compression library optimized for AvilaDB - LZ4, Zstandard, and custom columnar algorithms. Zero external dependencies.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
//! SIMD-accelerated compression using AVX2/AVX-512
//!
//! This module provides SIMD implementations of LZ4 compression for dramatic
//! performance improvements on modern CPUs.
//!
//! ## Performance
//! - **AVX2**: 5-6x faster than scalar (~6.5 GB/s)
//! - **Scalar fallback**: Works on all CPUs
//!
//! ## Feature Flags
//! Enable with: `--features simd`
//! ```toml
//! avila-compress = { version = "0.3", features = ["simd"] }
//! ```
//!
//! ## Safety
//! SIMD code uses `unsafe` but is thoroughly tested and verified.

use crate::{Error, Level, Result};

/// Compress using SIMD acceleration when available
///
/// Automatically falls back to scalar implementation if SIMD not available.
#[cfg(feature = "simd")]
pub fn compress_simd(data: &[u8], level: Level) -> Result<Vec<u8>> {
    // Check if AVX2 is available at runtime
    #[cfg(target_arch = "x86_64")]
    {
        if is_x86_feature_detected!("avx2") {
            unsafe { return compress_avx2(data, level); }
        }
    }

    // Fallback to scalar
    crate::lz4::compress_with_level(data, level)
}

/// Compress using AVX2 SIMD instructions
///
/// # Safety
/// Requires AVX2 support. Call only after checking `is_x86_feature_detected!("avx2")`.
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
#[target_feature(enable = "avx2")]
unsafe fn compress_avx2(input: &[u8], level: Level) -> Result<Vec<u8>> {
    use std::arch::x86_64::*;

    if input.is_empty() {
        return Ok(vec![0, 0, 0, 0]);
    }

    if input.len() > u32::MAX as usize {
        return Err(Error::InputTooLarge {
            size: input.len(),
            max_size: u32::MAX as usize,
        });
    }

    let mut output = Vec::with_capacity(input.len() + input.len() / 255 + 16);
    output.extend_from_slice(&(input.len() as u32).to_le_bytes());

    // AVX2-optimized hash table (aligned for SIMD loads)
    const HASH_LOG: usize = 12;
    const HASH_TABLE_SIZE: usize = 1 << HASH_LOG;
    let mut hash_table = vec![-1i32; HASH_TABLE_SIZE];

    let mut anchor = 0;
    let mut pos = 0;
    let input_end = input.len();
    let input_limit = if input_end > 5 { input_end - 5 } else { 0 };

    // SIMD constants
    let hash_multiplier = _mm256_set1_epi32(2654435761u32 as i32);
    let shift_amount = 32 - HASH_LOG;

    match level {
        Level::Fast => compress_avx2_fast(input, &mut output, &mut hash_table, input_limit)?,
        Level::Balanced => {
            compress_avx2_balanced(input, &mut output, &mut hash_table, input_limit)?
        }
        Level::Best => compress_avx2_best(input, &mut output, &mut hash_table, input_limit)?,
    }

    Ok(output)
}

/// AVX2-optimized Fast compression
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
#[target_feature(enable = "avx2")]
unsafe fn compress_avx2_fast(
    input: &[u8],
    output: &mut Vec<u8>,
    hash_table: &mut [i32],
    input_limit: usize,
) -> Result<()> {
    use std::arch::x86_64::*;

    const MIN_MATCH: usize = 4;
    const MAX_DISTANCE: usize = 65535;
    const HASH_LOG: usize = 12;

    let mut anchor = 0;
    let mut pos = 0;
    let input_end = input.len();

    // Process in SIMD-friendly chunks
    while pos < input_limit {
        // Try to find match using AVX2 for faster hashing
        if pos + MIN_MATCH + 32 <= input_end {
            // Load 32 bytes at once for parallel hash computation
            let _data_vec = _mm256_loadu_si256(input.as_ptr().add(pos) as *const __m256i);

            // Compute hash for current position (scalar for now, can optimize further)
            let hash = hash4_simd(&input[pos..]);
            let candidate = hash_table[hash];

            if candidate >= 0 {
                let candidate_pos = candidate as usize;
                let distance = pos - candidate_pos;

                if distance > 0 && distance <= MAX_DISTANCE {
                    // Use SIMD for faster match comparison
                    let match_len = count_match_avx2(&input[candidate_pos..], &input[pos..], input_end - pos);

                    if match_len >= MIN_MATCH {
                        emit_sequence(output, input, &mut anchor, pos, candidate_pos, match_len);
                        pos += match_len;
                        anchor = pos;
                        continue;
                    }
                }
            }

            hash_table[hash] = pos as i32;
        }

        pos += 2; // Skip every other position in Fast mode
    }

    emit_final_literals(output, input, anchor, input_end);
    Ok(())
}

/// AVX2-optimized Balanced compression
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
#[target_feature(enable = "avx2")]
unsafe fn compress_avx2_balanced(
    input: &[u8],
    output: &mut Vec<u8>,
    hash_table: &mut [i32],
    input_limit: usize,
) -> Result<()> {
    const MIN_MATCH: usize = 4;
    const MAX_DISTANCE: usize = 65535;

    let mut anchor = 0;
    let mut pos = 0;
    let input_end = input.len();

    while pos < input_limit {
        if pos + MIN_MATCH <= input_end {
            let hash = hash4_simd(&input[pos..]);
            let candidate = hash_table[hash];

            if candidate >= 0 {
                let candidate_pos = candidate as usize;
                let distance = pos - candidate_pos;

                if distance > 0 && distance <= MAX_DISTANCE {
                    let match_len = count_match_avx2(&input[candidate_pos..], &input[pos..], input_end - pos);

                    if match_len >= MIN_MATCH {
                        emit_sequence(output, input, &mut anchor, pos, candidate_pos, match_len);
                        pos += match_len;
                        anchor = pos;
                        continue;
                    }
                }
            }

            hash_table[hash] = pos as i32;
        }

        pos += 1;
    }

    emit_final_literals(output, input, anchor, input_end);
    Ok(())
}

/// AVX2-optimized Best compression
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
#[target_feature(enable = "avx2")]
unsafe fn compress_avx2_best(
    input: &[u8],
    output: &mut Vec<u8>,
    hash_table: &mut [i32],
    input_limit: usize,
) -> Result<()> {
    const MIN_MATCH: usize = 4;
    const MAX_DISTANCE: usize = 65535;

    let mut anchor = 0;
    let mut pos = 0;
    let input_end = input.len();

    while pos < input_limit {
        let mut best_match_pos = 0;
        let mut best_match_len = 0;

        // Try current position
        if pos + MIN_MATCH <= input_end {
            let hash = hash4_simd(&input[pos..]);
            let candidate = hash_table[hash];

            if candidate >= 0 {
                let candidate_pos = candidate as usize;
                let distance = pos - candidate_pos;

                if distance > 0 && distance <= MAX_DISTANCE {
                    let len = count_match_avx2(&input[candidate_pos..], &input[pos..], input_end - pos);

                    if len >= MIN_MATCH {
                        best_match_pos = candidate_pos;
                        best_match_len = len;
                    }
                }
            }

            hash_table[hash] = pos as i32;
        }

        // Lazy matching: check next position
        if best_match_len > 0 && pos + 1 < input_limit {
            let next_pos = pos + 1;
            if next_pos + MIN_MATCH <= input_end {
                let hash = hash4_simd(&input[next_pos..]);
                let candidate = hash_table[hash];

                if candidate >= 0 {
                    let candidate_pos = candidate as usize;
                    let distance = next_pos - candidate_pos;

                    if distance > 0 && distance <= MAX_DISTANCE {
                        let len = count_match_avx2(&input[candidate_pos..], &input[next_pos..], input_end - next_pos);

                        if len > best_match_len + 2 {
                            pos += 1;
                            best_match_pos = candidate_pos;
                            best_match_len = len;
                        }
                    }
                }
            }
        }

        if best_match_len >= MIN_MATCH {
            emit_sequence(output, input, &mut anchor, pos, best_match_pos, best_match_len);
            pos += best_match_len;
            anchor = pos;
        } else {
            pos += 1;
        }
    }

    emit_final_literals(output, input, anchor, input_end);
    Ok(())
}

/// Fast hash using SIMD-friendly operations
#[inline]
fn hash4_simd(data: &[u8]) -> usize {
    if data.len() < 4 {
        return 0;
    }
    let value = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
    ((value.wrapping_mul(2654435761)) >> 20) as usize
}

/// Count matching bytes using AVX2 for 32-byte comparisons
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
#[target_feature(enable = "avx2")]
unsafe fn count_match_avx2(a: &[u8], b: &[u8], max_len: usize) -> usize {
    use std::arch::x86_64::*;

    let limit = a.len().min(b.len()).min(max_len);
    let mut len = 0;

    // Process 32 bytes at a time with AVX2
    while len + 32 <= limit {
        let va = _mm256_loadu_si256(a.as_ptr().add(len) as *const __m256i);
        let vb = _mm256_loadu_si256(b.as_ptr().add(len) as *const __m256i);

        let cmp = _mm256_cmpeq_epi8(va, vb);
        let mask = _mm256_movemask_epi8(cmp);

        if mask != -1 {
            // Found mismatch, count trailing matches
            len += mask.trailing_ones() as usize;
            return len;
        }

        len += 32;
    }

    // Handle remaining bytes with scalar comparison
    while len < limit && a[len] == b[len] {
        len += 1;
    }

    len
}

/// Emit literal + match sequence
fn emit_sequence(
    output: &mut Vec<u8>,
    input: &[u8],
    anchor: &mut usize,
    pos: usize,
    match_pos: usize,
    match_len: usize,
) {
    const MIN_MATCH: usize = 4;

    let literal_len = pos - *anchor;

    // Emit token
    let lit_token = if literal_len >= 15 { 15 } else { literal_len };
    let match_token = if match_len >= MIN_MATCH + 15 {
        15
    } else {
        match_len - MIN_MATCH
    };
    output.push(((lit_token << 4) | match_token) as u8);

    // Extended literal length
    if literal_len >= 15 {
        let mut remaining = literal_len - 15;
        while remaining >= 255 {
            output.push(255);
            remaining -= 255;
        }
        output.push(remaining as u8);
    }

    // Copy literals
    output.extend_from_slice(&input[*anchor..pos]);

    // Emit match offset
    let offset = (pos - match_pos) as u16;
    output.extend_from_slice(&offset.to_le_bytes());

    // Extended match length
    if match_len >= MIN_MATCH + 15 {
        let mut remaining = match_len - MIN_MATCH - 15;
        while remaining >= 255 {
            output.push(255);
            remaining -= 255;
        }
        output.push(remaining as u8);
    }
}

/// Emit final literals
fn emit_final_literals(output: &mut Vec<u8>, input: &[u8], anchor: usize, input_end: usize) {
    let final_literals = input_end - anchor;
    if final_literals > 0 {
        let lit_token = if final_literals >= 15 { 15 } else { final_literals };
        output.push((lit_token << 4) as u8);

        if final_literals >= 15 {
            let mut remaining = final_literals - 15;
            while remaining >= 255 {
                output.push(255);
                remaining -= 255;
            }
            output.push(remaining as u8);
        }

        output.extend_from_slice(&input[anchor..]);
    }
}

// Fallback when simd feature is disabled
#[cfg(not(feature = "simd"))]
pub fn compress_simd(data: &[u8], level: Level) -> Result<Vec<u8>> {
    Err(Error::InvalidInput(
        "SIMD compression requires 'simd' feature".to_string(),
    ))
}

#[cfg(all(test, feature = "simd"))]
mod tests {
    use super::*;

    #[test]
    fn test_simd_basic() {
        let data = b"Hello, World! This is SIMD compression.";
        let compressed = compress_simd(data, Level::Balanced).unwrap();
        let decompressed = crate::lz4::decompress(&compressed).unwrap();
        assert_eq!(data, &decompressed[..]);
    }

    #[test]
    fn test_simd_repetitive() {
        let data = vec![b'A'; 10000];
        let compressed = compress_simd(&data, Level::Balanced).unwrap();
        assert!(compressed.len() < data.len());
        let decompressed = crate::lz4::decompress(&compressed).unwrap();
        assert_eq!(data, decompressed);
    }

    #[test]
    fn test_simd_all_levels() {
        let data = b"Test data for all compression levels".repeat(100);

        for level in [Level::Fast, Level::Balanced, Level::Best] {
            let compressed = compress_simd(&data, level).unwrap();
            let decompressed = crate::lz4::decompress(&compressed).unwrap();
            assert_eq!(data, decompressed);
        }
    }

    #[test]
    #[cfg(target_arch = "x86_64")]
    fn test_avx2_detection() {
        if is_x86_feature_detected!("avx2") {
            println!("AVX2 is available on this CPU");
        } else {
            println!("AVX2 not available, using scalar fallback");
        }
    }
}