refactor(cli): move adaptive feedback policy to Rust

The zstd file-I/O loop still kept adaptive compression's state machine in C: progression deltas, refresh gating, job-completion checks, input counters, speed decisions, and level updates were interleaved with private FIO and ZSTD state. That left orchestration policy behind the existing scalar Rust predicates.

Move the adaptive state and callback order into Rust. C now supplies scalar progression snapshots, the clock gate, exact diagnostics, and the ignored CCtx parameter setter through callbacks; private FIO_prefs_t, ZSTD_CCtx, ZSTD_frameProgression, clocks, and progress formatting remain C-owned. Preserve the previous-progression publication before backlog evaluation, input counter reset points, and serial/MT level-clamp behavior.

Test Plan:

- cargo +nightly fmt --manifest-path rust/Cargo.toml --all -- --check

- git diff --check

- ulimit -v 41943040; CARGO_BUILD_JOBS=1 make -j1
This commit is contained in:
2026-07-21 11:32:06 +02:00
parent 1a3971ff10
commit 1554c5aacb
2 changed files with 594 additions and 159 deletions
+428 -3
View File
@@ -948,6 +948,15 @@ pub const FIO_RUST_ZSTD_ADAPT_BLOCKED_INPUT: c_int = 3;
pub const FIO_RUST_ZSTD_ADAPT_LEVEL_SLOWER: c_int = 4;
pub const FIO_RUST_ZSTD_ADAPT_LEVEL_FASTER: c_int = 5;
pub const FIO_RUST_ZSTD_ADAPT_DIAG_OUTPUT_BLOCKED: c_int = 0;
pub const FIO_RUST_ZSTD_ADAPT_DIAG_OUTPUT_BACKLOG: c_int = 1;
pub const FIO_RUST_ZSTD_ADAPT_DIAG_CHECK: c_int = 2;
pub const FIO_RUST_ZSTD_ADAPT_DIAG_INPUT_STARVATION: c_int = 3;
pub const FIO_RUST_ZSTD_ADAPT_DIAG_INPUT_STATS: c_int = 4;
pub const FIO_RUST_ZSTD_ADAPT_DIAG_RECOMMEND_FASTER: c_int = 5;
pub const FIO_RUST_ZSTD_ADAPT_DIAG_SLOWER_LEVEL: c_int = 6;
pub const FIO_RUST_ZSTD_ADAPT_DIAG_FASTER_LEVEL: c_int = 7;
/// Scalar inputs for the adaptive decision policy. C keeps the
/// `FIO_rust_zstd_projection_context_t`, `ZSTD_frameProgression`, and
/// preference layouts private; this projection carries only the values used
@@ -1030,9 +1039,44 @@ const _: () = {
);
};
/// Rust owns only the scalar adaptive predicates and compression-level
/// normalization. C retains the surrounding iteration order, diagnostics,
/// progression snapshots, and `ZSTD_CCtx_setParameter()` mutation.
/// Scalar frame progression returned by C's progression callback. The public
/// C structure itself never crosses this boundary.
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct FIO_rust_zstd_progression_t {
pub ingested: u64,
pub consumed: u64,
pub produced: u64,
pub flushed: u64,
pub current_job_id: c_uint,
pub nb_active_workers: c_uint,
}
const _: () = {
assert!(std::mem::offset_of!(FIO_rust_zstd_progression_t, ingested) == 0);
assert!(std::mem::offset_of!(FIO_rust_zstd_progression_t, consumed) == size_of::<u64>());
assert!(std::mem::offset_of!(FIO_rust_zstd_progression_t, produced) == 2 * size_of::<u64>());
assert!(std::mem::offset_of!(FIO_rust_zstd_progression_t, flushed) == 3 * size_of::<u64>());
assert!(
std::mem::offset_of!(FIO_rust_zstd_progression_t, current_job_id) == 4 * size_of::<u64>()
);
assert!(
std::mem::offset_of!(FIO_rust_zstd_progression_t, nb_active_workers)
== 4 * size_of::<u64>() + size_of::<c_uint>()
);
assert!(
size_of::<FIO_rust_zstd_progression_t>() == 4 * size_of::<u64>() + 2 * size_of::<c_uint>()
);
};
pub type FIO_rust_zstd_adaptive_refresh_fn = unsafe extern "C" fn(*mut c_void) -> c_int;
pub type FIO_rust_zstd_adaptive_progression_fn =
unsafe extern "C" fn(*mut c_void, *mut FIO_rust_zstd_progression_t);
pub type FIO_rust_zstd_adaptive_set_parameter_fn = unsafe extern "C" fn(*mut c_void, c_int);
pub type FIO_rust_zstd_adaptive_diagnostic_fn =
unsafe extern "C" fn(*mut c_void, c_int, *const FIO_rust_zstd_adapt_projection_t);
/// Rust owns the scalar adaptive state, progression ordering, and level
/// decision. C retains diagnostics and all codec/private state in callbacks.
#[repr(C)]
pub struct FIO_rust_zstd_compress_projection_t {
pub read_opaque: *mut c_void,
@@ -1049,7 +1093,46 @@ pub struct FIO_rust_zstd_compress_projection_t {
pub compress_stream: Option<FIO_rust_zstd_compress_stream_fn>,
pub iteration: Option<FIO_rust_zstd_iteration_fn>,
pub compress_display: Option<FIO_rust_zstd_compress_display_fn>,
pub adaptive_mode: c_int,
pub nb_workers: c_int,
pub min_adapt_level: c_int,
pub max_adapt_level: c_int,
pub max_c_level: c_int,
pub adaptive_refresh: Option<FIO_rust_zstd_adaptive_refresh_fn>,
pub adaptive_progression: Option<FIO_rust_zstd_adaptive_progression_fn>,
pub adaptive_set_parameter: Option<FIO_rust_zstd_adaptive_set_parameter_fn>,
pub adaptive_diagnostic: Option<FIO_rust_zstd_adaptive_diagnostic_fn>,
}
const _: () = {
let callback_offset = (14 * size_of::<usize>() + 5 * size_of::<c_int>() + size_of::<usize>()
- 1)
/ size_of::<usize>()
* size_of::<usize>();
assert!(
std::mem::offset_of!(FIO_rust_zstd_compress_projection_t, adaptive_mode)
== 14 * size_of::<usize>()
);
assert!(
std::mem::offset_of!(FIO_rust_zstd_compress_projection_t, nb_workers)
== 14 * size_of::<usize>() + size_of::<c_int>()
);
assert!(
std::mem::offset_of!(FIO_rust_zstd_compress_projection_t, max_c_level)
== 14 * size_of::<usize>() + 4 * size_of::<c_int>()
);
assert!(
std::mem::offset_of!(FIO_rust_zstd_compress_projection_t, adaptive_refresh)
== callback_offset
);
assert!(
std::mem::offset_of!(FIO_rust_zstd_compress_projection_t, adaptive_diagnostic)
== callback_offset + 3 * size_of::<usize>()
);
assert!(
size_of::<FIO_rust_zstd_compress_projection_t>()
== callback_offset + 4 * size_of::<usize>()
);
};
pub const FIO_RUST_GZIP_OK: c_int = 0;
pub const FIO_RUST_GZIP_INIT_ERROR: c_int = 1;
@@ -3440,6 +3523,214 @@ pub unsafe extern "C" fn FIO_rust_zstd_adapt(
zstd_adapt_policy(policy, projection)
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
struct ZstdAdaptiveState {
previous_update: FIO_rust_zstd_progression_t,
previous_correction: FIO_rust_zstd_progression_t,
speed_change: c_int,
flush_waiting: c_uint,
input_presented: c_uint,
input_blocked: c_uint,
last_job_id: c_uint,
}
unsafe fn zstd_adaptive_report(
projection: &FIO_rust_zstd_compress_projection_t,
diagnostic: c_int,
adapt_projection: Option<&FIO_rust_zstd_adapt_projection_t>,
) {
let Some(report) = projection.adaptive_diagnostic else {
return;
};
let projection_ptr = adapt_projection.map_or(ptr::null(), |value| value as *const _);
unsafe {
report(projection.policy_opaque, diagnostic, projection_ptr);
}
}
unsafe fn zstd_adaptive_iteration(
state: &mut ZstdAdaptiveState,
projection: &FIO_rust_zstd_compress_projection_t,
compression_level: &mut c_int,
old_input_pos: usize,
new_input_pos: usize,
to_flush_now: usize,
) {
state.input_presented = state.input_presented.wrapping_add(1);
if old_input_pos == new_input_pos {
state.input_blocked = state.input_blocked.wrapping_add(1);
}
if to_flush_now == 0 {
state.flush_waiting = 1;
}
if projection.adaptive_mode == 0 {
return;
}
let Some(refresh) = projection.adaptive_refresh else {
return;
};
if unsafe { refresh(projection.policy_opaque) } == 0 {
return;
}
let Some(get_progression) = projection.adaptive_progression else {
return;
};
let mut progression = FIO_rust_zstd_progression_t::default();
unsafe {
get_progression(projection.policy_opaque, &mut progression);
}
if progression.current_job_id > 1 {
let newly_produced = progression
.produced
.wrapping_sub(state.previous_update.produced);
let newly_flushed = progression
.flushed
.wrapping_sub(state.previous_update.flushed);
assert!(progression.produced >= state.previous_update.produced);
assert!(projection.nb_workers >= 1);
let mut adapt_projection = FIO_rust_zstd_adapt_projection_t {
consumed: progression.consumed,
previous_consumed: state.previous_update.consumed,
nb_active_workers: progression.nb_active_workers,
..FIO_rust_zstd_adapt_projection_t::default()
};
if zstd_adapt_policy(FIO_RUST_ZSTD_ADAPT_OUTPUT_BLOCKED, &adapt_projection)
== FIO_RUST_ZSTD_ADAPT_SLOWER
{
unsafe {
zstd_adaptive_report(projection, FIO_RUST_ZSTD_ADAPT_DIAG_OUTPUT_BLOCKED, None);
}
state.speed_change = FIO_RUST_ZSTD_ADAPT_SLOWER;
}
state.previous_update = progression;
adapt_projection.newly_produced = newly_produced;
adapt_projection.newly_flushed = newly_flushed;
adapt_projection.flush_waiting = state.flush_waiting;
if zstd_adapt_policy(FIO_RUST_ZSTD_ADAPT_OUTPUT_BACKLOG, &adapt_projection)
== FIO_RUST_ZSTD_ADAPT_SLOWER
{
unsafe {
zstd_adaptive_report(
projection,
FIO_RUST_ZSTD_ADAPT_DIAG_OUTPUT_BACKLOG,
Some(&adapt_projection),
);
}
state.speed_change = FIO_RUST_ZSTD_ADAPT_SLOWER;
}
state.flush_waiting = 0;
}
if progression.current_job_id > state.last_job_id {
unsafe {
zstd_adaptive_report(projection, FIO_RUST_ZSTD_ADAPT_DIAG_CHECK, None);
}
if progression.current_job_id > projection.nb_workers.wrapping_add(1) as c_uint {
let mut adapt_projection = FIO_rust_zstd_adapt_projection_t {
input_blocked: state.input_blocked,
..FIO_rust_zstd_adapt_projection_t::default()
};
if zstd_adapt_policy(FIO_RUST_ZSTD_ADAPT_INPUT_STARVATION, &adapt_projection)
== FIO_RUST_ZSTD_ADAPT_SLOWER
{
unsafe {
zstd_adaptive_report(
projection,
FIO_RUST_ZSTD_ADAPT_DIAG_INPUT_STARVATION,
None,
);
}
state.speed_change = FIO_RUST_ZSTD_ADAPT_SLOWER;
} else if state.speed_change == FIO_RUST_ZSTD_ADAPT_NO_CHANGE {
let newly_ingested = progression
.ingested
.wrapping_sub(state.previous_correction.ingested);
let newly_consumed = progression
.consumed
.wrapping_sub(state.previous_correction.consumed);
let newly_produced = progression
.produced
.wrapping_sub(state.previous_correction.produced);
let newly_flushed = progression
.flushed
.wrapping_sub(state.previous_correction.flushed);
state.previous_correction = progression;
adapt_projection.input_blocked = state.input_blocked;
adapt_projection.input_presented = state.input_presented;
adapt_projection.newly_ingested = newly_ingested;
adapt_projection.newly_consumed = newly_consumed;
adapt_projection.newly_produced = newly_produced;
adapt_projection.newly_flushed = newly_flushed;
unsafe {
zstd_adaptive_report(
projection,
FIO_RUST_ZSTD_ADAPT_DIAG_INPUT_STATS,
Some(&adapt_projection),
);
}
if zstd_adapt_policy(FIO_RUST_ZSTD_ADAPT_BLOCKED_INPUT, &adapt_projection)
== FIO_RUST_ZSTD_ADAPT_FASTER
{
unsafe {
zstd_adaptive_report(
projection,
FIO_RUST_ZSTD_ADAPT_DIAG_RECOMMEND_FASTER,
Some(&adapt_projection),
);
}
state.speed_change = FIO_RUST_ZSTD_ADAPT_FASTER;
}
}
state.input_blocked = 0;
state.input_presented = 0;
}
if state.speed_change == FIO_RUST_ZSTD_ADAPT_SLOWER {
unsafe {
zstd_adaptive_report(projection, FIO_RUST_ZSTD_ADAPT_DIAG_SLOWER_LEVEL, None);
}
let adapt_level_projection = FIO_rust_zstd_adapt_projection_t {
compression_level: *compression_level,
max_adapt_level: projection.max_adapt_level,
max_c_level: projection.max_c_level,
..FIO_rust_zstd_adapt_projection_t::default()
};
*compression_level =
zstd_adapt_policy(FIO_RUST_ZSTD_ADAPT_LEVEL_SLOWER, &adapt_level_projection);
if let Some(set_parameter) = projection.adaptive_set_parameter {
unsafe {
set_parameter(projection.policy_opaque, *compression_level);
}
}
}
if state.speed_change == FIO_RUST_ZSTD_ADAPT_FASTER {
unsafe {
zstd_adaptive_report(projection, FIO_RUST_ZSTD_ADAPT_DIAG_FASTER_LEVEL, None);
}
let adapt_level_projection = FIO_rust_zstd_adapt_projection_t {
compression_level: *compression_level,
min_adapt_level: projection.min_adapt_level,
..FIO_rust_zstd_adapt_projection_t::default()
};
*compression_level =
zstd_adapt_policy(FIO_RUST_ZSTD_ADAPT_LEVEL_FASTER, &adapt_level_projection);
if let Some(set_parameter) = projection.adaptive_set_parameter {
unsafe {
set_parameter(projection.policy_opaque, *compression_level);
}
}
}
state.speed_change = FIO_RUST_ZSTD_ADAPT_NO_CHANGE;
state.last_job_id = progression.current_job_id;
}
}
type FIO_rust_zstd_to_flush_now_fn = unsafe extern "C" fn(*mut c_void) -> usize;
type FIO_rust_zstd_compress_stream2_fn =
unsafe extern "C" fn(*mut c_void, *mut ZSTD_outBuffer, *mut ZSTD_inBuffer, c_int) -> usize;
@@ -3582,6 +3873,14 @@ pub unsafe extern "C" fn FIO_rust_compressZstdFrame(
let Some(iteration) = projection.iteration else {
return FIO_RUST_ZSTD_INVALID_PROJECTION;
};
if projection.adaptive_mode != 0
&& (projection.adaptive_refresh.is_none()
|| projection.adaptive_progression.is_none()
|| projection.adaptive_set_parameter.is_none()
|| projection.adaptive_diagnostic.is_none())
{
return FIO_RUST_ZSTD_INVALID_PROJECTION;
}
let mut job = ptr::null_mut::<c_void>();
let mut output = ptr::null_mut::<u8>();
@@ -3605,6 +3904,7 @@ pub unsafe extern "C" fn FIO_rust_compressZstdFrame(
let mut out_file_size = 0_u64;
let mut directive = FIO_RUST_ZSTD_E_CONTINUE;
let mut compression_level = compression_level;
let mut adaptive_state = ZstdAdaptiveState::default();
loop {
if input_pos == input_size {
@@ -3692,6 +3992,14 @@ pub unsafe extern "C" fn FIO_rust_compressZstdFrame(
}
unsafe {
zstd_adaptive_iteration(
&mut adaptive_state,
projection,
&mut compression_level,
old_input_pos,
new_input_pos,
to_flush_now,
);
iteration(
projection.policy_opaque,
src_file_name,
@@ -7338,6 +7646,114 @@ mod tests {
}
}
#[derive(Default)]
struct AdaptiveCallbackState {
progression: FIO_rust_zstd_progression_t,
refresh_calls: usize,
events: Vec<c_int>,
}
unsafe extern "C" fn adaptive_test_refresh(opaque: *mut c_void) -> c_int {
let state = unsafe { &mut *opaque.cast::<AdaptiveCallbackState>() };
state.refresh_calls += 1;
1
}
unsafe extern "C" fn adaptive_test_progression(
opaque: *mut c_void,
progression: *mut FIO_rust_zstd_progression_t,
) {
let state = unsafe { &*opaque.cast::<AdaptiveCallbackState>() };
unsafe {
*progression = state.progression;
}
}
unsafe extern "C" fn adaptive_test_set_parameter(opaque: *mut c_void, level: c_int) {
let state = unsafe { &mut *opaque.cast::<AdaptiveCallbackState>() };
state.events.push(100 + level);
}
unsafe extern "C" fn adaptive_test_diagnostic(
opaque: *mut c_void,
diagnostic: c_int,
_projection: *const FIO_rust_zstd_adapt_projection_t,
) {
let state = unsafe { &mut *opaque.cast::<AdaptiveCallbackState>() };
state.events.push(diagnostic);
}
fn adaptive_test_projection(
state: &mut AdaptiveCallbackState,
) -> FIO_rust_zstd_compress_projection_t {
let opaque = (state as *mut AdaptiveCallbackState).cast::<c_void>();
FIO_rust_zstd_compress_projection_t {
read_opaque: ptr::null_mut(),
write_opaque: ptr::null_mut(),
codec_opaque: ptr::null_mut(),
policy_opaque: opaque,
read_buffer_size: 1,
read_fill: None,
read_consume: None,
write_acquire: None,
write_enqueue: None,
write_release: None,
sparse_write_end: None,
compress_stream: None,
iteration: None,
compress_display: None,
adaptive_mode: 1,
nb_workers: 1,
min_adapt_level: 1,
max_adapt_level: 5,
max_c_level: 22,
adaptive_refresh: Some(adaptive_test_refresh),
adaptive_progression: Some(adaptive_test_progression),
adaptive_set_parameter: Some(adaptive_test_set_parameter),
adaptive_diagnostic: Some(adaptive_test_diagnostic),
}
}
#[test]
fn zstd_adaptive_iteration_preserves_callback_order_and_resets_state() {
let mut callbacks = AdaptiveCallbackState {
progression: FIO_rust_zstd_progression_t {
produced: 10,
flushed: 8,
current_job_id: 3,
..FIO_rust_zstd_progression_t::default()
},
..AdaptiveCallbackState::default()
};
let projection = adaptive_test_projection(&mut callbacks);
let mut state = ZstdAdaptiveState::default();
let mut compression_level = 3;
unsafe {
zstd_adaptive_iteration(&mut state, &projection, &mut compression_level, 0, 1, 1);
}
assert_eq!(
callbacks.events,
vec![
FIO_RUST_ZSTD_ADAPT_DIAG_OUTPUT_BLOCKED,
FIO_RUST_ZSTD_ADAPT_DIAG_OUTPUT_BACKLOG,
FIO_RUST_ZSTD_ADAPT_DIAG_CHECK,
FIO_RUST_ZSTD_ADAPT_DIAG_INPUT_STARVATION,
FIO_RUST_ZSTD_ADAPT_DIAG_SLOWER_LEVEL,
104,
]
);
assert_eq!(callbacks.refresh_calls, 1);
assert_eq!(compression_level, 4);
assert_eq!(state.previous_update, callbacks.progression);
assert_eq!(state.last_job_id, 3);
assert_eq!(state.speed_change, FIO_RUST_ZSTD_ADAPT_NO_CHANGE);
assert_eq!(state.input_blocked, 0);
assert_eq!(state.input_presented, 0);
assert_eq!(state.flush_waiting, 0);
}
unsafe extern "C" fn zstd_test_read_fill(
opaque: *mut c_void,
requested: usize,
@@ -7487,6 +7903,15 @@ mod tests {
compress_stream: Some(zstd_test_compress),
iteration: Some(zstd_test_iteration),
compress_display: None,
adaptive_mode: 0,
nb_workers: 0,
min_adapt_level: 0,
max_adapt_level: 0,
max_c_level: 0,
adaptive_refresh: None,
adaptive_progression: None,
adaptive_set_parameter: None,
adaptive_diagnostic: None,
}
}