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
72 changes: 47 additions & 25 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
Loading