Skip to content
Merged
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ diesel_migrations = { version = "2.3.2", default-features = false, features = ["
divan = { package = "codspeed-divan-compat", version = "5.0.1" }
env_logger = { version = "0.11", default-features = false }
event-listener = { version = "5", default-features = false }
# `std` (default) rather than the `libm` path: wacore-appstate is already
# std-only (LazyLock), and libm would add a dependency to serve float ops
# this crate never touches.
fearless_simd = { version = "0.6.0" }
flate2 = { version = "1.1.9", default-features = false, features = ["zlib-rs"] }
futures = { version = "0.3", default-features = false, features = ["alloc", "async-await"] }
getrandom = { version = "0.4", default-features = false }
Expand Down
6 changes: 5 additions & 1 deletion wacore/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ ignored = ["getrandom"]

[features]
default = ["simd"]
simd = ["wacore-appstate/simd"]
# Two independent SIMD backends behind one switch: appstate's LTHash runs on
# fearless_simd (stable, runtime-dispatched), wacore-binary's packed codec
# still on `portable_simd` (nightly). Enabling both here keeps this feature's
# meaning — "SIMD everywhere it exists" — unchanged for consumers.
simd = ["wacore-appstate/simd", "wacore-binary/simd"]
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated
debug-snapshots = []
# Typed interop with the decoded libsignal SessionRecord v1 model.
legacy-session-interop = ["wacore-libsignal/legacy-session-interop"]
Expand Down
8 changes: 7 additions & 1 deletion wacore/appstate/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,17 @@ crate-type = ["rlib"]

[features]
default = ["simd"]
simd = ["wacore-binary/simd"]
# LTHash lane math via fearless_simd: runtime level detection on stable, so
# this feature no longer drags the crate onto nightly. It deliberately does
# NOT chain to `wacore-binary/simd` — that one is still `portable_simd` and
# nightly-only; `wacore/simd` enables both so the top-level surface is
# unchanged.
simd = ["dep:fearless_simd"]

[dependencies]
anyhow = { workspace = true }
buffa = { workspace = true }
fearless_simd = { workspace = true, optional = true }
hex = { workspace = true }
hkdf = { workspace = true }
hmac = { workspace = true }
Expand Down
1 change: 0 additions & 1 deletion wacore/appstate/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#![cfg_attr(feature = "simd", feature(portable_simd))]
pub mod decode;
pub mod encode;
pub mod errors;
Expand Down
149 changes: 108 additions & 41 deletions wacore/appstate/src/lthash.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#[cfg(feature = "simd")]
use core::simd::u16x8;
use fearless_simd::{Level, Simd, SimdBase, dispatch, u16x8};
use hkdf::Hkdf;
use hmac::digest::KeyInit;
use hmac::{Hmac, Mac};
Expand All @@ -12,6 +12,12 @@ use std::sync::LazyLock;
static EXTRACT_HMAC: LazyLock<Hmac<Sha256>> =
LazyLock::new(|| Hmac::<Sha256>::new_from_slice(&[0u8; 32]).expect("32-byte HMAC key"));

/// Probing CPU features is comparatively expensive and the answer cannot
/// change while the process runs, so the level is resolved once here:
/// `multiple_op` dispatches once per operand, and a patch carries hundreds.
#[cfg(feature = "simd")]
static SIMD_LEVEL: LazyLock<Level> = LazyLock::new(Level::new);

#[derive(Clone, Debug)]
pub struct LTHash {
pub hkdf_info: &'static [u8],
Expand Down Expand Up @@ -77,30 +83,7 @@ fn perform_pointwise_with_overflow(base: &mut [u8], input: &[u8], subtract: bool
let (base_chunks, base_rem) = base_remaining.as_chunks_mut::<16>();
let (input_chunks, input_rem) = input_remaining.as_chunks::<16>();

for (base_chunk, input_chunk) in base_chunks.iter_mut().zip(input_chunks) {
// `from_le_bytes` per lane states the wire endianness directly, so
// the same code is correct on either host; on little-endian it
// lowers to the plain 16-byte load a transmute would have emitted.
let base_arr: [u16; 8] = core::array::from_fn(|i| {
u16::from_le_bytes([base_chunk[2 * i], base_chunk[2 * i + 1]])
});
let input_arr: [u16; 8] = core::array::from_fn(|i| {
u16::from_le_bytes([input_chunk[2 * i], input_chunk[2 * i + 1]])
});
let base_simd = u16x8::from_array(base_arr);
let input_simd = u16x8::from_array(input_arr);

let result_simd = if subtract {
base_simd - input_simd
} else {
base_simd + input_simd
};

let out = result_simd.to_array();
for (base_pair, lane) in base_chunk.as_chunks_mut::<2>().0.iter_mut().zip(out) {
*base_pair = lane.to_le_bytes();
}
}
dispatch!(*SIMD_LEVEL, simd => pointwise_chunks(simd, base_chunks, input_chunks, subtract));
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated

base_remaining = base_rem;
input_remaining = input_rem;
Expand All @@ -124,6 +107,45 @@ fn perform_pointwise_with_overflow(base: &mut [u8], input: &[u8], subtract: bool
}
}

/// The lane math, generic over the SIMD level `dispatch!` picked at runtime.
/// `#[inline(always)]` is load-bearing rather than a hint: it is what makes
/// each instantiation inherit its level's `target_feature` set, so the AVX2
/// copy lowers to AVX2 instead of to the baseline the caller was compiled for.
#[cfg(feature = "simd")]
#[inline(always)]
fn pointwise_chunks<S: Simd>(
simd: S,
base_chunks: &mut [[u8; 16]],
input_chunks: &[[u8; 16]],
subtract: bool,
) {
for (base_chunk, input_chunk) in base_chunks.iter_mut().zip(input_chunks) {
// `from_le_bytes` per lane states the wire endianness directly, so
// the same code is correct on either host; on little-endian it
// lowers to the plain 16-byte load a transmute would have emitted.
let base_arr: [u16; 8] = core::array::from_fn(|i| {
u16::from_le_bytes([base_chunk[2 * i], base_chunk[2 * i + 1]])
});
let input_arr: [u16; 8] = core::array::from_fn(|i| {
u16::from_le_bytes([input_chunk[2 * i], input_chunk[2 * i + 1]])
});
let base_simd = u16x8::from_slice(simd, &base_arr);
let input_simd = u16x8::from_slice(simd, &input_arr);

let result_simd = if subtract {
base_simd - input_simd
} else {
base_simd + input_simd
};

let mut out = [0u16; 8];
result_simd.store_slice(&mut out);
for (base_pair, lane) in base_chunk.as_chunks_mut::<2>().0.iter_mut().zip(out) {
*base_pair = lane.to_le_bytes();
}
}
}

fn hkdf_sha256_into(key: &[u8], info: &[u8], out: &mut [u8]) {
let mut extract = EXTRACT_HMAC.clone();
extract.update(key);
Expand Down Expand Up @@ -170,27 +192,72 @@ mod tests {
}

#[test]
fn test_simd_determinism_and_consistency() {
fn add_then_subtract_returns_to_zero_across_sizes() {
let test_sizes = [2, 4, 8, 16, 18, 32, 64, 128, 256];

for &size in &test_sizes {
let mut base_simd = vec![0u8; size];
let mut base_scalar = vec![0u8; size];
let mut base = vec![0u8; size];
let input = vec![1u8; size];

perform_pointwise_with_overflow(&mut base_simd, &input, false);
perform_pointwise_with_overflow(&mut base_scalar, &input, false);
assert_eq!(base_simd, base_scalar, "Add failed for size {}", size);

perform_pointwise_with_overflow(&mut base_simd, &input, true);
perform_pointwise_with_overflow(&mut base_scalar, &input, true);
assert_eq!(base_simd, base_scalar, "Subtract failed for size {}", size);
assert_eq!(
base_simd,
vec![0u8; size],
"Subtract result incorrect for size {}",
size
);
perform_pointwise_with_overflow(&mut base, &input, false);
perform_pointwise_with_overflow(&mut base, &input, true);
assert_eq!(base, vec![0u8; size], "size {size}");
}
}

/// Straight-line reference: no chunking, no dispatch, no feature gate. The
/// point is to be obviously correct rather than fast, so that the test
/// below is an independent check on the SIMD path rather than a
/// comparison of that path against itself.
fn reference_pointwise(base: &mut [u8], input: &[u8], subtract: bool) {
for (b, i) in base.chunks_exact_mut(2).zip(input.chunks_exact(2)) {
let x = u16::from_le_bytes([b[0], b[1]]);
let y = u16::from_le_bytes([i[0], i[1]]);
let r = if subtract {
x.wrapping_sub(y)
} else {
x.wrapping_add(y)
};
b.copy_from_slice(&r.to_le_bytes());
}
}

/// The SIMD path splits into 16-byte chunks and leaves a scalar tail, so
/// the sizes below straddle that boundary: under one chunk, exactly one,
/// chunk-plus-tail, and several chunks. Inputs are seeded to cover the
/// wrap boundaries in both directions, which is where a lane-width or
/// endianness mistake would show up rather than in round-number data.
#[test]
fn simd_path_matches_independent_scalar_reference() {
let sizes = [0usize, 2, 14, 16, 18, 32, 34, 128, 130, 256];
// Deterministic LCG: reproducible failures, no dev-dependency.
let mut seed = 0x2545_F491u32;
let mut next = move || {
seed = seed.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
(seed >> 24) as u8
};

for size in sizes {
for subtract in [false, true] {
for edge in [0u8, 0xFF, 0x01] {
let base: Vec<u8> = (0..size)
.map(|i| if i % 3 == 0 { edge } else { next() })
.collect();
let input: Vec<u8> = (0..size)
.map(|i| if i % 5 == 0 { edge } else { next() })
.collect();

let mut actual = base.clone();
let mut expected = base.clone();
perform_pointwise_with_overflow(&mut actual, &input, subtract);
reference_pointwise(&mut expected, &input, subtract);

assert_eq!(
actual, expected,
"size {size}, subtract {subtract}, edge {edge:#04x}"
);
}
}
}
}

Expand Down
Loading