feat(rust): port FSE compression primitives

Move FSE normalization, count-header writing, compression-table construction,
and stream encoding from C to Rust. The original C source remains as a
header-only shim, preserving the public header configuration while native
consumers receive the Rust archive exports.

This completes both codec directions for FSE in the migration crate and lets
the unchanged C compatibility tests exercise the Rust encoder implementation.

Test Plan:
- cargo clippy; cargo clippy --benches; cargo clippy --tests
- cargo +nightly fmt, then repeat the Clippy checks
- cargo test --all-targets
- cargo build --release
- Compile the C shim with strict warnings enabled
- 1,000-case pristine-C differential for tables, headers, and payloads
- ./tests/fuzzer -v -T10s
- ./tests/decodecorpus -t -T5

Refs: rust/README.md
This commit is contained in:
2026-07-10 20:32:18 +02:00
parent ebc43676b7
commit 5f9d607ddd
4 changed files with 1089 additions and 622 deletions
+1085
View File
@@ -0,0 +1,1085 @@
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
use crate::bitstream::{
BIT_CStream_t, BIT_addBits, BIT_closeCStream, BIT_flushBits, BIT_flushBitsFast, BIT_initCStream,
};
use crate::entropy_common::FSE_MIN_TABLELOG;
use crate::errors::{ERR_isError, ZstdErrorCode, ERROR};
use std::mem::size_of;
use std::os::raw::{c_int, c_short, c_uint, c_void};
pub const FSE_DEFAULT_TABLELOG: u32 = 11;
pub const FSE_NCOUNTBOUND: usize = 512;
pub const FSE_MAX_TABLELOG: u32 = 12;
const FSE_TABLELOG_ABSOLUTE_MAX: u32 = 15;
#[repr(C)]
#[derive(Clone, Copy)]
struct FSE_symbolCompressionTransform {
deltaFindState: c_int,
deltaNbBits: u32,
}
#[repr(C)]
struct FSE_CState_t {
value: isize,
stateTable: *const c_void,
symbolTT: *const c_void,
stateLog: u32,
}
#[inline]
fn FSE_TABLESTEP(table_size: usize) -> usize {
(table_size >> 1) + (table_size >> 3) + 3
}
#[inline]
fn fse_table_size(table_log: u32) -> Option<usize> {
if table_log > FSE_TABLELOG_ABSOLUTE_MAX || table_log >= usize::BITS {
return None;
}
Some(1usize << table_log)
}
#[inline]
fn fse_ctable_transform_offset(table_log: u32) -> usize {
// `((U32*)ct) + 1 + (tableLog ? tableSize >> 1 : 1)` in fse.h.
4 + if table_log == 0 {
4
} else {
(1usize << table_log) * 2
}
}
#[inline]
fn fse_build_ctable_workspace_size(max_symbol_value: u32, table_log: u32) -> Option<usize> {
let table_size = fse_table_size(table_log)?;
let words = (max_symbol_value as usize)
.checked_add(2)?
.checked_add(table_size)?
/ 2
+ size_of::<u64>() / size_of::<u32>();
words.checked_mul(size_of::<u32>())
}
#[inline]
unsafe fn fse_read_u16(base: *const u8, index: usize) -> u16 {
(base.add(index * size_of::<u16>()) as *const u16).read_unaligned()
}
#[inline]
unsafe fn fse_write_u16(base: *mut u8, index: usize, value: u16) {
(base.add(index * size_of::<u16>()) as *mut u16).write_unaligned(value);
}
#[inline]
unsafe fn fse_read_transform(
ctable: *const c_uint,
table_log: u32,
symbol: usize,
) -> FSE_symbolCompressionTransform {
let base = ctable as *const u8;
(base.add(
fse_ctable_transform_offset(table_log)
+ symbol * size_of::<FSE_symbolCompressionTransform>(),
) as *const FSE_symbolCompressionTransform)
.read_unaligned()
}
#[inline]
unsafe fn fse_write_transform(
ctable: *mut c_uint,
table_log: u32,
symbol: usize,
transform: FSE_symbolCompressionTransform,
) {
let base = ctable as *mut u8;
(base.add(
fse_ctable_transform_offset(table_log)
+ symbol * size_of::<FSE_symbolCompressionTransform>(),
) as *mut FSE_symbolCompressionTransform)
.write_unaligned(transform);
}
#[inline]
unsafe fn fse_read_normalized(normalized_counter: *const c_short, symbol: u32) -> i16 {
*normalized_counter.add(symbol as usize)
}
#[inline]
unsafe fn fse_read_count(count: *const c_uint, symbol: u32) -> u32 {
*count.add(symbol as usize)
}
#[inline]
unsafe fn fse_write_normalized(normalized_counter: *mut c_short, symbol: u32, value: i16) {
*normalized_counter.add(symbol as usize) = value;
}
#[no_mangle]
pub unsafe extern "C" fn FSE_buildCTable_wksp(
ct: *mut c_uint,
normalized_counter: *const c_short,
max_symbol_value: c_uint,
table_log: c_uint,
work_space: *mut c_void,
wksp_size: usize,
) -> usize {
let table_size = match fse_table_size(table_log) {
Some(size) => size,
None => return ERROR(ZstdErrorCode::TableLogTooLarge),
};
let required_wksp = match fse_build_ctable_workspace_size(max_symbol_value, table_log) {
Some(size) => size,
None => return ERROR(ZstdErrorCode::TableLogTooLarge),
};
if required_wksp > wksp_size {
return ERROR(ZstdErrorCode::TableLogTooLarge);
}
let table_mask = table_size - 1;
let max_sv1 = max_symbol_value as usize + 1;
let ctable = ct as *mut u8;
let workspace = work_space as *mut u8;
let cumul = workspace;
let table_symbol = cumul.add((max_sv1 + 1) * size_of::<u16>());
let mut high_threshold = table_size - 1;
// CTable header: tableLog and maxSymbolValue are two native-endian U16s.
fse_write_u16(ctable, 0, table_log as u16);
fse_write_u16(ctable, 1, max_symbol_value as u16);
fse_write_u16(cumul, 0, 0);
for symbol_plus_one in 1..=max_sv1 {
let symbol = (symbol_plus_one - 1) as u32;
let previous = fse_read_u16(cumul, symbol_plus_one - 1);
let normalized = fse_read_normalized(normalized_counter, symbol);
if normalized == -1 {
fse_write_u16(cumul, symbol_plus_one, previous.wrapping_add(1));
*table_symbol.add(high_threshold) = symbol as u8;
high_threshold = high_threshold.wrapping_sub(1);
} else {
debug_assert!(normalized >= 0);
fse_write_u16(
cumul,
symbol_plus_one,
previous.wrapping_add(normalized as u16),
);
}
}
fse_write_u16(cumul, max_sv1, (table_size + 1) as u16);
if high_threshold == table_size - 1 {
// The C implementation stores the sequential symbol list immediately
// past tableSymbol, then permutes it into tableSymbol. Keep that
// workspace layout so callers can provide exactly the C macro size.
let spread = table_symbol.add(table_size);
let mut position = 0usize;
for symbol in 0..max_sv1 {
let frequency = fse_read_normalized(normalized_counter, symbol as u32);
debug_assert!(frequency >= 0);
for offset in 0..frequency as usize {
*spread.add(position + offset) = symbol as u8;
}
position += frequency as usize;
}
let step = FSE_TABLESTEP(table_size);
let mut position = 0usize;
for source_index in (0..table_size).step_by(2) {
for unroll in 0..2 {
let target = (position + unroll * step) & table_mask;
*table_symbol.add(target) = *spread.add(source_index + unroll);
}
position = (position + 2 * step) & table_mask;
}
debug_assert_eq!(position, 0);
} else {
let step = FSE_TABLESTEP(table_size);
let mut position = 0usize;
for symbol in 0..max_sv1 {
let frequency = fse_read_normalized(normalized_counter, symbol as u32);
for _ in 0..frequency.max(0) as usize {
*table_symbol.add(position) = symbol as u8;
position = (position + step) & table_mask;
while position > high_threshold {
position = (position + step) & table_mask;
}
}
}
debug_assert_eq!(position, 0);
}
// tableU16 is `(U16*)ct + 2`, so its first entry is U16 index 2.
for table_index in 0..table_size {
let symbol = *table_symbol.add(table_index) as usize;
let current = fse_read_u16(cumul, symbol);
fse_write_u16(
ctable,
2 + current as usize,
(table_size + table_index) as u16,
);
fse_write_u16(cumul, symbol, current.wrapping_add(1));
}
let mut total = 0u32;
for symbol in 0..=max_symbol_value {
let normalized = fse_read_normalized(normalized_counter, symbol);
let transform = match normalized {
0 => FSE_symbolCompressionTransform {
deltaFindState: 0,
deltaNbBits: ((table_log + 1) << 16).wrapping_sub(1u32 << table_log),
},
-1 | 1 => {
let transform = FSE_symbolCompressionTransform {
deltaFindState: total.wrapping_sub(1) as i32,
deltaNbBits: (table_log << 16).wrapping_sub(1u32 << table_log),
};
total = total.wrapping_add(1);
transform
}
_ => {
debug_assert!(normalized > 1);
let normalized = normalized as u32;
let max_bits_out = table_log - (31 - (normalized - 1).leading_zeros());
let min_state_plus = normalized << max_bits_out;
let transform = FSE_symbolCompressionTransform {
deltaFindState: total.wrapping_sub(normalized) as i32,
deltaNbBits: (max_bits_out << 16).wrapping_sub(min_state_plus),
};
total = total.wrapping_add(normalized);
transform
}
};
fse_write_transform(ct, table_log, symbol as usize, transform);
}
0
}
#[no_mangle]
pub extern "C" fn FSE_NCountWriteBound(max_symbol_value: c_uint, table_log: c_uint) -> usize {
if max_symbol_value == 0 {
return FSE_NCOUNTBOUND;
}
(((max_symbol_value as usize + 1) * table_log as usize + 4 + 2) / 8) + 1 + 2
}
unsafe fn fse_write_two_bytes(
buffer: *mut u8,
buffer_size: usize,
out_pos: &mut usize,
bit_stream: u32,
) -> Result<(), usize> {
if buffer_size.saturating_sub(*out_pos) < 2 {
return Err(ERROR(ZstdErrorCode::DstSizeTooSmall));
}
*buffer.add(*out_pos) = bit_stream as u8;
*buffer.add(*out_pos + 1) = (bit_stream >> 8) as u8;
*out_pos += 2;
Ok(())
}
unsafe fn FSE_writeNCount_generic(
header: *mut c_void,
header_buffer_size: usize,
normalized_counter: *const c_short,
max_symbol_value: c_uint,
table_log: c_uint,
) -> usize {
let buffer = header as *mut u8;
let table_size = 1i32 << table_log;
let mut bit_stream = table_log - FSE_MIN_TABLELOG;
let mut bit_count = 4i32;
let mut remaining = table_size + 1;
let mut threshold = table_size;
let mut nb_bits = table_log as i32 + 1;
let mut symbol = 0u32;
let alphabet_size = max_symbol_value.wrapping_add(1);
let mut previous_is_zero = false;
let mut out_pos = 0usize;
while symbol < alphabet_size && remaining > 1 {
if previous_is_zero {
let mut start = symbol;
while symbol < alphabet_size && fse_read_normalized(normalized_counter, symbol) == 0 {
symbol = symbol.wrapping_add(1);
}
if symbol == alphabet_size {
break;
}
while symbol >= start.wrapping_add(24) {
start = start.wrapping_add(24);
bit_stream = bit_stream.wrapping_add(0xffffu32 << bit_count);
if let Err(error) =
fse_write_two_bytes(buffer, header_buffer_size, &mut out_pos, bit_stream)
{
return error;
}
bit_stream >>= 16;
// The 16 just-written repeat bits exactly replace the flushed
// bits, so C deliberately leaves bitCount unchanged here.
}
while symbol >= start.wrapping_add(3) {
start = start.wrapping_add(3);
bit_stream = bit_stream.wrapping_add(3u32 << bit_count);
bit_count += 2;
}
bit_stream = bit_stream.wrapping_add((symbol - start) << bit_count);
bit_count += 2;
if bit_count > 16 {
if let Err(error) =
fse_write_two_bytes(buffer, header_buffer_size, &mut out_pos, bit_stream)
{
return error;
}
bit_stream >>= 16;
bit_count -= 16;
}
}
let mut count = fse_read_normalized(normalized_counter, symbol) as i32;
symbol = symbol.wrapping_add(1);
let max = (2 * threshold - 1) - remaining;
remaining -= count.abs();
count += 1;
if count >= threshold {
count += max;
}
bit_stream = bit_stream.wrapping_add((count as u32) << bit_count);
bit_count += nb_bits;
bit_count -= i32::from(count < max);
previous_is_zero = count == 1;
if remaining < 1 {
return ERROR(ZstdErrorCode::Generic);
}
while remaining < threshold {
nb_bits -= 1;
threshold >>= 1;
}
if bit_count > 16 {
if let Err(error) =
fse_write_two_bytes(buffer, header_buffer_size, &mut out_pos, bit_stream)
{
return error;
}
bit_stream >>= 16;
bit_count -= 16;
}
}
if remaining != 1 {
return ERROR(ZstdErrorCode::Generic);
}
if let Err(error) = fse_write_two_bytes(buffer, header_buffer_size, &mut out_pos, bit_stream) {
return error;
}
out_pos - 2 + ((bit_count + 7) as usize / 8)
}
#[no_mangle]
pub unsafe extern "C" fn FSE_writeNCount(
buffer: *mut c_void,
buffer_size: usize,
normalized_counter: *const c_short,
max_symbol_value: c_uint,
table_log: c_uint,
) -> usize {
if table_log > FSE_MAX_TABLELOG {
return ERROR(ZstdErrorCode::TableLogTooLarge);
}
if table_log < FSE_MIN_TABLELOG {
return ERROR(ZstdErrorCode::Generic);
}
FSE_writeNCount_generic(
buffer,
buffer_size,
normalized_counter,
max_symbol_value,
table_log,
)
}
#[inline]
fn fse_highbit32(value: u32) -> u32 {
31 - value.leading_zeros()
}
fn FSE_minTableLog(src_size: usize, max_symbol_value: u32) -> u32 {
debug_assert!(src_size > 1);
let min_bits_src = fse_highbit32(src_size as u32) + 1;
let min_bits_symbols = fse_highbit32(max_symbol_value) + 2;
min_bits_src.min(min_bits_symbols)
}
#[no_mangle]
pub extern "C" fn FSE_optimalTableLog_internal(
max_table_log: c_uint,
src_size: usize,
max_symbol_value: c_uint,
minus: c_uint,
) -> c_uint {
let max_bits_src = fse_highbit32(src_size.wrapping_sub(1) as u32).wrapping_sub(minus);
let min_bits = FSE_minTableLog(src_size, max_symbol_value);
let mut table_log = if max_table_log == 0 {
FSE_DEFAULT_TABLELOG
} else {
max_table_log
};
if max_bits_src < table_log {
table_log = max_bits_src;
}
if min_bits > table_log {
table_log = min_bits;
}
table_log.clamp(FSE_MIN_TABLELOG, FSE_MAX_TABLELOG)
}
#[no_mangle]
pub extern "C" fn FSE_optimalTableLog(
max_table_log: c_uint,
src_size: usize,
max_symbol_value: c_uint,
) -> c_uint {
FSE_optimalTableLog_internal(max_table_log, src_size, max_symbol_value, 2)
}
unsafe fn FSE_normalizeM2(
norm: *mut c_short,
table_log: u32,
count: *const c_uint,
mut total: usize,
max_symbol_value: u32,
low_prob_count: i16,
) -> usize {
const NOT_YET_ASSIGNED: i16 = -2;
let mut distributed = 0u32;
let low_threshold = (total >> table_log) as u32;
let mut low_one = ((total * 3) >> (table_log + 1)) as u32;
for symbol in 0..=max_symbol_value {
let current_count = fse_read_count(count, symbol);
if current_count == 0 {
fse_write_normalized(norm, symbol, 0);
} else if current_count <= low_threshold {
fse_write_normalized(norm, symbol, low_prob_count);
distributed += 1;
total -= current_count as usize;
} else if current_count <= low_one {
fse_write_normalized(norm, symbol, 1);
distributed += 1;
total -= current_count as usize;
} else {
fse_write_normalized(norm, symbol, NOT_YET_ASSIGNED);
}
}
let mut to_distribute = (1u32 << table_log) - distributed;
if to_distribute == 0 {
return 0;
}
if (total / to_distribute as usize) > low_one as usize {
low_one = ((total * 3) / (to_distribute as usize * 2)) as u32;
for symbol in 0..=max_symbol_value {
if fse_read_normalized(norm, symbol) == NOT_YET_ASSIGNED
&& fse_read_count(count, symbol) <= low_one
{
fse_write_normalized(norm, symbol, 1);
distributed += 1;
total -= fse_read_count(count, symbol) as usize;
}
}
to_distribute = (1u32 << table_log) - distributed;
}
if distributed == max_symbol_value + 1 {
let mut max_value = 0u32;
let mut max_count = 0u32;
for symbol in 0..=max_symbol_value {
let current_count = fse_read_count(count, symbol);
if current_count > max_count {
max_value = symbol;
max_count = current_count;
}
}
let value = fse_read_normalized(norm, max_value).wrapping_add(to_distribute as i16);
fse_write_normalized(norm, max_value, value);
return 0;
}
if total == 0 {
let mut symbol = 0u32;
while to_distribute > 0 {
if fse_read_normalized(norm, symbol) > 0 {
to_distribute -= 1;
let value = fse_read_normalized(norm, symbol).wrapping_add(1);
fse_write_normalized(norm, symbol, value);
}
symbol = (symbol + 1) % (max_symbol_value + 1);
}
return 0;
}
let v_step_log = 62 - table_log;
let mid = (1u64 << (v_step_log - 1)) - 1;
let r_step = (((1u64 << v_step_log) * to_distribute as u64) + mid) / total as u64;
let mut tmp_total = mid;
for symbol in 0..=max_symbol_value {
if fse_read_normalized(norm, symbol) == NOT_YET_ASSIGNED {
let end = tmp_total + fse_read_count(count, symbol) as u64 * r_step;
let start_weight = (tmp_total >> v_step_log) as u32;
let end_weight = (end >> v_step_log) as u32;
let weight = end_weight - start_weight;
if weight < 1 {
return ERROR(ZstdErrorCode::Generic);
}
fse_write_normalized(norm, symbol, weight as i16);
tmp_total = end;
}
}
0
}
#[no_mangle]
pub unsafe extern "C" fn FSE_normalizeCount(
normalized_counter: *mut c_short,
mut table_log: c_uint,
count: *const c_uint,
total: usize,
max_symbol_value: c_uint,
use_low_prob_count: c_uint,
) -> usize {
if table_log == 0 {
table_log = FSE_DEFAULT_TABLELOG;
}
if table_log < FSE_MIN_TABLELOG {
return ERROR(ZstdErrorCode::Generic);
}
if table_log > FSE_MAX_TABLELOG {
return ERROR(ZstdErrorCode::TableLogTooLarge);
}
if table_log < FSE_minTableLog(total, max_symbol_value) {
return ERROR(ZstdErrorCode::Generic);
}
const RTB_TABLE: [u32; 8] = [
0, 473_195, 504_333, 520_860, 550_000, 700_000, 750_000, 830_000,
];
let low_prob_count = if use_low_prob_count != 0 { -1 } else { 1 };
let scale = 62 - table_log;
let step = (1u64 << 62) / total as u32 as u64;
let v_step = 1u64 << (scale - 20);
let mut still_to_distribute = 1i32 << table_log;
let mut largest = 0u32;
let mut largest_probability = 0i16;
let low_threshold = (total >> table_log) as u32;
for symbol in 0..=max_symbol_value {
let current_count = fse_read_count(count, symbol);
if current_count as usize == total {
return 0;
}
if current_count == 0 {
fse_write_normalized(normalized_counter, symbol, 0);
} else if current_count <= low_threshold {
fse_write_normalized(normalized_counter, symbol, low_prob_count);
still_to_distribute -= 1;
} else {
let mut probability = ((current_count as u64 * step) >> scale) as i16;
if probability < 8 {
let rest_to_beat = v_step * RTB_TABLE[probability as usize] as u64;
if current_count as u64 * step - ((probability as u64) << scale) > rest_to_beat {
probability += 1;
}
}
if probability > largest_probability {
largest_probability = probability;
largest = symbol;
}
fse_write_normalized(normalized_counter, symbol, probability);
still_to_distribute -= probability as i32;
}
}
if -still_to_distribute >= (fse_read_normalized(normalized_counter, largest) >> 1) as i32 {
let error_code = FSE_normalizeM2(
normalized_counter,
table_log,
count,
total,
max_symbol_value,
low_prob_count,
);
if ERR_isError(error_code) {
return error_code;
}
} else {
let value = fse_read_normalized(normalized_counter, largest)
.wrapping_add(still_to_distribute as i16);
fse_write_normalized(normalized_counter, largest, value);
}
table_log as usize
}
#[no_mangle]
pub unsafe extern "C" fn FSE_buildCTable_rle(ct: *mut c_uint, symbol_value: u8) -> usize {
let ctable = ct as *mut u8;
fse_write_u16(ctable, 0, 0);
fse_write_u16(ctable, 1, symbol_value as u16);
fse_write_u16(ctable, 2, 0);
fse_write_u16(ctable, 3, 0);
fse_write_transform(
ct,
0,
symbol_value as usize,
FSE_symbolCompressionTransform {
deltaFindState: 0,
deltaNbBits: 0,
},
);
0
}
unsafe fn FSE_initCState(state: *mut FSE_CState_t, ct: *const c_uint) {
let ctable = ct as *const u8;
let table_log = fse_read_u16(ctable, 0) as u32;
*state = FSE_CState_t {
value: (1usize << table_log) as isize,
stateTable: ctable.add(4) as *const c_void,
symbolTT: ctable.add(fse_ctable_transform_offset(table_log)) as *const c_void,
stateLog: table_log,
};
}
unsafe fn FSE_initCState2(state: *mut FSE_CState_t, ct: *const c_uint, symbol: u32) {
FSE_initCState(state, ct);
let transform = fse_read_transform(ct, (*state).stateLog, symbol as usize);
let nb_bits_out = transform.deltaNbBits.wrapping_add(1 << 15) >> 16;
let value = (nb_bits_out << 16).wrapping_sub(transform.deltaNbBits);
(*state).value = value as isize;
let index = ((*state).value >> nb_bits_out) + transform.deltaFindState as isize;
(*state).value = ((*state).stateTable as *const u16)
.add(index as usize)
.read_unaligned() as isize;
}
unsafe fn FSE_encodeSymbol(bit_stream: *mut BIT_CStream_t, state: *mut FSE_CState_t, symbol: u32) {
let transform = ((*state).symbolTT as *const FSE_symbolCompressionTransform)
.add(symbol as usize)
.read_unaligned();
let nb_bits_out = (((*state).value as u64 + transform.deltaNbBits as u64) >> 16) as u32;
BIT_addBits(bit_stream, (*state).value as usize, nb_bits_out);
let index = ((*state).value >> nb_bits_out) + transform.deltaFindState as isize;
(*state).value = ((*state).stateTable as *const u16)
.add(index as usize)
.read_unaligned() as isize;
}
unsafe fn FSE_flushCState(bit_stream: *mut BIT_CStream_t, state: *const FSE_CState_t) {
BIT_addBits(bit_stream, (*state).value as usize, (*state).stateLog);
BIT_flushBits(bit_stream);
}
unsafe fn FSE_compress_usingCTable_generic(
dst: *mut c_void,
dst_size: usize,
src: *const c_void,
mut src_size: usize,
ct: *const c_uint,
fast: bool,
) -> usize {
if src_size <= 2 {
return 0;
}
let mut bit_stream = std::mem::zeroed::<BIT_CStream_t>();
if ERR_isError(BIT_initCStream(&mut bit_stream, dst, dst_size)) {
return 0;
}
let start = src as *const u8;
let mut input = start.add(src_size);
let mut state1 = std::mem::zeroed::<FSE_CState_t>();
let mut state2 = std::mem::zeroed::<FSE_CState_t>();
if src_size & 1 != 0 {
input = input.sub(1);
FSE_initCState2(&mut state1, ct, *input as u32);
input = input.sub(1);
FSE_initCState2(&mut state2, ct, *input as u32);
input = input.sub(1);
FSE_encodeSymbol(&mut bit_stream, &mut state1, *input as u32);
if fast {
BIT_flushBitsFast(&mut bit_stream);
} else {
BIT_flushBits(&mut bit_stream);
}
} else {
input = input.sub(1);
FSE_initCState2(&mut state2, ct, *input as u32);
input = input.sub(1);
FSE_initCState2(&mut state1, ct, *input as u32);
}
src_size -= 2;
if usize::BITS > FSE_MAX_TABLELOG * 4 + 7 && src_size & 2 != 0 {
input = input.sub(1);
FSE_encodeSymbol(&mut bit_stream, &mut state2, *input as u32);
input = input.sub(1);
FSE_encodeSymbol(&mut bit_stream, &mut state1, *input as u32);
if fast {
BIT_flushBitsFast(&mut bit_stream);
} else {
BIT_flushBits(&mut bit_stream);
}
}
while input > start {
input = input.sub(1);
FSE_encodeSymbol(&mut bit_stream, &mut state2, *input as u32);
if usize::BITS < FSE_MAX_TABLELOG * 2 + 7 {
if fast {
BIT_flushBitsFast(&mut bit_stream);
} else {
BIT_flushBits(&mut bit_stream);
}
}
input = input.sub(1);
FSE_encodeSymbol(&mut bit_stream, &mut state1, *input as u32);
if usize::BITS > FSE_MAX_TABLELOG * 4 + 7 {
input = input.sub(1);
FSE_encodeSymbol(&mut bit_stream, &mut state2, *input as u32);
input = input.sub(1);
FSE_encodeSymbol(&mut bit_stream, &mut state1, *input as u32);
}
if fast {
BIT_flushBitsFast(&mut bit_stream);
} else {
BIT_flushBits(&mut bit_stream);
}
}
FSE_flushCState(&mut bit_stream, &state2);
FSE_flushCState(&mut bit_stream, &state1);
BIT_closeCStream(&mut bit_stream)
}
#[no_mangle]
pub unsafe extern "C" fn FSE_compress_usingCTable(
dst: *mut c_void,
dst_size: usize,
src: *const c_void,
src_size: usize,
ct: *const c_uint,
) -> usize {
let block_bound = src_size
.wrapping_add(src_size >> 7)
.wrapping_add(4)
.wrapping_add(size_of::<usize>());
FSE_compress_usingCTable_generic(dst, dst_size, src, src_size, ct, dst_size >= block_bound)
}
#[no_mangle]
pub extern "C" fn FSE_compressBound(size: usize) -> usize {
FSE_NCOUNTBOUND
.wrapping_add(size)
.wrapping_add(size >> 7)
.wrapping_add(4)
.wrapping_add(size_of::<usize>())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fse_decompress::FSE_decompress_wksp_bmi2;
use std::slice;
const NO_LOW_NORM: [i16; 5] = [5, 4, 7, 6, 10];
const NO_LOW_SOURCE: [u8; 29] = [
0, 1, 2, 3, 4, 3, 2, 1, 0, 4, 2, 4, 3, 1, 0, 4, 2, 3, 4, 4, 0, 1, 2, 3, 4, 0, 2, 1, 4,
];
const LOW_NORM: [i16; 5] = [6, -1, 5, 8, 12];
const LOW_SOURCE: [u8; 33] = [
4, 3, 2, 1, 0, 4, 4, 3, 2, 4, 0, 1, 4, 3, 2, 0, 4, 4, 3, 2, 1, 0, 4, 3, 2, 4, 0, 4, 3, 2,
1, 4, 0,
];
fn bytes(hex: &str) -> Vec<u8> {
assert_eq!(hex.len() % 2, 0);
hex.as_bytes()
.chunks_exact(2)
.map(|pair| {
fn digit(value: u8) -> u8 {
match value {
b'0'..=b'9' => value - b'0',
b'a'..=b'f' => value - b'a' + 10,
_ => panic!("invalid hex digit"),
}
}
(digit(pair[0]) << 4) | digit(pair[1])
})
.collect()
}
fn ctable_storage(table_log: u32, max_symbol_value: u32) -> Vec<u32> {
vec![0; 1 + (1usize << (table_log - 1)) + (max_symbol_value as usize + 1) * 2]
}
fn workspace(table_log: u32, max_symbol_value: u32) -> Vec<u32> {
let bytes = fse_build_ctable_workspace_size(max_symbol_value, table_log).unwrap();
vec![0; bytes.div_ceil(4)]
}
unsafe fn build_table(norm: &[i16], table_log: u32) -> Vec<u32> {
let mut table = ctable_storage(table_log, norm.len() as u32 - 1);
let mut wksp = workspace(table_log, norm.len() as u32 - 1);
assert_eq!(
FSE_buildCTable_wksp(
table.as_mut_ptr(),
norm.as_ptr(),
norm.len() as u32 - 1,
table_log,
wksp.as_mut_ptr().cast(),
wksp.len() * size_of::<u32>(),
),
0
);
table
}
unsafe fn compress(norm: &[i16], source: &[u8]) -> (Vec<u8>, Vec<u8>) {
let table = build_table(norm, 5);
let mut payload = vec![0u8; FSE_compressBound(source.len())];
let written = FSE_compress_usingCTable(
payload.as_mut_ptr().cast(),
payload.len(),
source.as_ptr().cast(),
source.len(),
table.as_ptr(),
);
assert_ne!(written, 0);
payload.truncate(written);
let mut header = vec![0u8; FSE_NCOUNTBOUND];
let header_size = FSE_writeNCount(
header.as_mut_ptr().cast(),
header.len(),
norm.as_ptr(),
norm.len() as u32 - 1,
5,
);
assert!(!ERR_isError(header_size));
header.truncate(header_size);
(header, payload)
}
unsafe fn compress_with_capacity(norm: &[i16], source: &[u8], capacity: usize) -> Vec<u8> {
let table = build_table(norm, 5);
let mut payload = vec![0u8; capacity];
let written = FSE_compress_usingCTable(
payload.as_mut_ptr().cast(),
payload.len(),
source.as_ptr().cast(),
source.len(),
table.as_ptr(),
);
payload.truncate(written);
payload
}
unsafe fn decompress(encoded: &[u8], capacity: usize) -> (usize, Vec<u8>) {
let mut output = vec![0u8; capacity];
let mut workspace = vec![0u32; 6000];
let result = FSE_decompress_wksp_bmi2(
output.as_mut_ptr().cast(),
output.len(),
encoded.as_ptr().cast(),
encoded.len(),
FSE_MAX_TABLELOG,
workspace.as_mut_ptr().cast(),
workspace.len() * size_of::<u32>(),
0,
);
(result, output)
}
#[test]
fn ctable_layout_matches_pristine_c_for_both_spread_paths() {
let no_low = unsafe { build_table(&NO_LOW_NORM, 5) };
let low = unsafe { build_table(&LOW_NORM, 5) };
let no_low_bytes = unsafe {
slice::from_raw_parts(
no_low.as_ptr().cast::<u8>(),
no_low.len() * size_of::<u32>(),
)
};
let low_bytes = unsafe {
slice::from_raw_parts(low.as_ptr().cast::<u8>(), low.len() * size_of::<u32>())
};
// Fixture generated by the pristine HEAD C implementation.
assert_eq!(
no_low_bytes,
bytes("05000400200025002e0037003c0021002a0033003800220026002b002f00340039003d00230027002c00300035003e002400280029002d003100320036003a003b003f00fbffffffd8ff020001000000c0ff030002000000c8ff02000a000000d0ff02000c000000d8ff0100")
);
assert_eq!(
low_bytes,
bytes("05000400200025002e00330037003c003f00210026002a002f003800220027002b003000340039003d003e0023002400280029002c002d0031003200350036003a003b00faffffffd0ff020005000000e0ff040002000000d8ff020004000000c0ff020008000000d0ff0100")
);
}
#[test]
fn ncount_and_payloads_are_bit_exact_against_pristine_c() {
let (no_low_header, no_low_payload) = unsafe { compress(&NO_LOW_NORM, &NO_LOW_SOURCE) };
let (low_header, low_payload) = unsafe { compress(&LOW_NORM, &LOW_SOURCE) };
assert_eq!(no_low_header, bytes("600aba07"));
assert_eq!(no_low_payload, bytes("f4d9b22b7166ab4b7001"));
assert_eq!(low_header, bytes("70c0e403"));
assert_eq!(low_payload, bytes("e0fb60f4a95030d396fb1d"));
}
#[test]
fn bounded_ctable_stream_path_matches_the_fast_stream() {
for (norm, source, expected) in [
(
&NO_LOW_NORM[..],
&NO_LOW_SOURCE[..],
bytes("f4d9b22b7166ab4b7001"),
),
(
&LOW_NORM[..],
&LOW_SOURCE[..],
bytes("e0fb60f4a95030d396fb1d"),
),
] {
let capacity = source.len() + (source.len() >> 7) + 4 + size_of::<usize>() - 1;
assert_eq!(
unsafe { compress_with_capacity(norm, source, capacity) },
expected
);
assert!(unsafe { compress_with_capacity(norm, source, 8) }.is_empty());
}
}
#[test]
fn fse_streams_round_trip_through_the_rust_decoder() {
for (norm, source) in [
(&NO_LOW_NORM[..], &NO_LOW_SOURCE[..]),
(&LOW_NORM[..], &LOW_SOURCE[..]),
] {
let (header, payload) = unsafe { compress(norm, source) };
let mut encoded = header;
encoded.extend_from_slice(&payload);
let (result, decoded) = unsafe { decompress(&encoded, source.len()) };
assert_eq!(result, source.len());
assert_eq!(&decoded[..result], source);
}
}
#[test]
fn normalization_and_public_bounds_match_pristine_c() {
let count = [3u32, 1, 4, 1, 5, 9, 2, 6];
let mut normalized = [0i16; 8];
let result =
unsafe { FSE_normalizeCount(normalized.as_mut_ptr(), 5, count.as_ptr(), 31, 7, 1) };
assert_eq!(result, 5);
assert_eq!(normalized, [3, 1, 4, 1, 5, 10, 2, 6]);
assert_eq!(FSE_NCountWriteBound(4, 5), 6);
assert_eq!(FSE_compressBound(33), 557);
assert_eq!(FSE_optimalTableLog(12, 33, 4), 5);
}
#[test]
fn rle_ctable_uses_the_c_layout() {
let mut table = vec![0xa5a5_a5a5u32; 1 + 1 + (256 * 2)];
assert_eq!(unsafe { FSE_buildCTable_rle(table.as_mut_ptr(), 127) }, 0);
let bytes = unsafe { slice::from_raw_parts(table.as_ptr().cast::<u8>(), table.len() * 4) };
assert_eq!(&bytes[..8], &[0, 0, 127, 0, 0, 0, 0, 0]);
let transform_offset =
fse_ctable_transform_offset(0) + 127 * size_of::<FSE_symbolCompressionTransform>();
assert_eq!(&bytes[transform_offset..transform_offset + 8], &[0; 8]);
}
#[test]
fn compact_buffer_path_does_not_overwrite_its_boundary() {
let count = [1i16, 0, 1, 0, 1, 0, 1, 0, 1, 9, 18];
let mut storage = [0xa5u8; 11];
let result =
unsafe { FSE_writeNCount(storage.as_mut_ptr().cast(), 9, count.as_ptr(), 10, 5) };
assert!(ERR_isError(result) || result <= 9);
assert_eq!(&storage[9..], &[0xa5, 0xa5]);
}
#[test]
fn generated_normalized_counts_round_trip() {
let mut state = 0x7a4d_3e21u32;
for case_index in 0..128u32 {
state = state.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
let table_log = 5 + state % 4;
let max_symbol_value = 3 + (state >> 8) % 36;
let mut normalized = vec![0i16; max_symbol_value as usize + 1];
let mut remaining = 1u32 << table_log;
for symbol in 0..max_symbol_value {
state = state
.wrapping_mul(1_664_525)
.wrapping_add(1_013_904_223 ^ case_index);
// Reserve one unit for the final symbol. This is the valid
// normalized-count contract expected by FSE_writeNCount().
let weight = state % remaining;
normalized[symbol as usize] = if weight == 1 && state & 1 != 0 {
-1
} else {
weight as i16
};
remaining -= weight;
}
normalized[max_symbol_value as usize] = if remaining == 1 && state & 2 != 0 {
-1
} else {
remaining as i16
};
let mut header = [0u8; FSE_NCOUNTBOUND];
let written = unsafe {
FSE_writeNCount(
header.as_mut_ptr().cast(),
header.len(),
normalized.as_ptr(),
max_symbol_value,
table_log,
)
};
assert!(
!ERR_isError(written),
"case {case_index}, table_log {table_log}, normalized {normalized:?}, written {written}"
);
let mut decoded = vec![0i16; 256];
let mut decoded_max = 255u32;
let mut decoded_log = 0u32;
let read = unsafe {
crate::entropy_common::FSE_readNCount(
decoded.as_mut_ptr(),
&mut decoded_max,
&mut decoded_log,
header.as_ptr().cast(),
written,
)
};
assert_eq!(read, written, "case {case_index}");
assert_eq!(decoded_max, max_symbol_value, "case {case_index}");
assert_eq!(decoded_log, table_log, "case {case_index}");
assert_eq!(
&decoded[..normalized.len()],
normalized.as_slice(),
"case {case_index}"
);
}
}
}
+1
View File
@@ -7,6 +7,7 @@ pub mod cpu;
pub mod debug;
pub mod entropy_common;
pub mod errors;
pub mod fse_compress;
pub mod fse_decompress;
pub mod hist;
pub mod mem;