Skip to content

Commit 34843b3

Browse files
committed
Add abs_diff
1 parent 7cbebed commit 34843b3

8 files changed

Lines changed: 108 additions & 3 deletions

File tree

.claude/skills/thermite.zip

451 Bytes
Binary file not shown.

.claude/skills/thermite/references/masks.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ m.all() -> bool // every lane true
1818
m.any() -> bool // some lane true
1919
m.none() -> bool // no lane true (== !any)
2020

21+
// scanning: turn a mask into find/count primitives (build on native_bitmask)
22+
m.first_set() -> Option<usize> // index of lowest true lane; with cmp_eq this is a SIMD memchr
23+
m.last_set() -> Option<usize> // index of highest true lane
24+
m.count_set() -> usize // number of true lanes (popcount)
25+
2126
m.select(t, f) -> S // per-lane: mask ? t : f (one instruction; S: GenericSelectable)
2227

2328
// bitwise combine

.claude/skills/thermite/references/trait-hierarchy.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@ instruction sequences per backend at zero runtime cost
8080

8181
- **GenericVector**: construction, lane access, load/store, gather/scatter, lookup,
8282
widen/narrow (`extend`/`narrow`/`concat`/`split`), interleave/deinterleave,
83-
reverse/swap_bytes/compress, cast/into_bits, `zz`/`nz`, prefix/suffix mask,
84-
map/fold/reduce. -> [vector-api.md](vector-api.md) section 1.
83+
reverse/swap_bytes/compress, `align::<OFFSET>` (two-vector lane window), cast/into_bits,
84+
`zz`/`nz`, prefix/suffix mask, map/fold/reduce. -> [vector-api.md](vector-api.md) section 1.
8585
- **BitwiseVector / BitshiftVector**: `&` `|` `^` `!`, `bitandnot`, `ternlog`/`bilog`;
8686
`shl`/`shr`/`shlv`/`shrv`/`shli`/`shri`, byte shifts, rotates, `reverse_bits`.
8787
-> section 2.
@@ -94,7 +94,8 @@ instruction sequences per backend at zero runtime cost
9494
`NEG_ONE`, `MIN_POSITIVE`. -> section 5.
9595
- **IntegerVector family**: `mulhi`/`mullo`, `saturating_add/sub`, `wrapping_sum/prod`,
9696
dividers, `count_ones/zeros`, `leading_ones/zeros`; signed `srai/sra/srav`,
97-
`avg_floor/ceil`; unsigned `is_power_of_two`, `avg`, `parity`, `ilog2p1`. -> section 6.
97+
`avg_floor/ceil`, `mulhrs` (rounded Q-format multiply); unsigned `is_power_of_two`,
98+
`avg`, `parity`, `ilog2p1`, `abs_diff`, `in_range`. -> section 6.
9899
- **FloatVector**: `sqrt`, `rcp`, `rsqrt`, `floor/ceil/round/trunc/fract`, `mix`,
99100
`next_up/down`, `mul_sign`, `signed_zero`, `one_minus_sq`, classification
100101
(`is_nan/finite/infinite/normal/subnormal`), constants

.claude/skills/thermite/references/vector-api.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ let (lo, hi) = a.interleave(b) let (a, b) = lo.deinterleave(hi)
4545
v.compress(mask) // stable left-pack of true lanes (AVX-512 vpcompress; portable fallback)
4646
v.compress_z(mask) // left-pack true lanes, zero the rest
4747

48+
// Two-vector element align (palignr family; any element type): the window of
49+
// LANES lanes starting at lane OFFSET of [a, b]. OFFSET=0 -> a, OFFSET=LANES -> b.
50+
a.align::<OFFSET>(b) // sliding window across a load boundary; int backends use native byte aligns
51+
4852
// Casting
4953
v.cast::<W>() // numeric cast, like `as`
5054
v.fast_cast::<W>() // faster, may skip edge cases
@@ -112,8 +116,11 @@ v.wrapping_sum() v.wrapping_prod() v.count_ones() v.count_zeros()
112116
v.leading_ones() v.leading_zeros()
113117
// SignedIntegerVector
114118
v.srai::<I>() v.sra(n) v.srav(unsigned) a.avg_floor(b) a.avg_ceil(b)
119+
a.mulhrs(b) // rounded Q(W-1) fixed-point multiply (i16: Q15, x86 PMULHRSW); rounds, not truncates
115120
// UnsignedIntegerVector
116121
v.is_power_of_two() -> M a.avg(b) v.parity() v.ilog2p1() v.next_power_of_two_m1()
122+
a.abs_diff(b) // |a - b| without overflow (saturating-sub form)
123+
x.in_range(lo, hi) -> M // mask of lo <= x <= hi, inclusive (branchless, one compare)
117124

118125
// Division by a precomputed divisor (constant-time, branchfree). See `divider` module.
119126
use thermite::{BranchfreeDivider, Divider};

crates/thermite/src/register/mod.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1828,6 +1828,18 @@ pub trait UnsignedIntegerRegister:
18281828
Self::sub(Self::bitor(a, b), Self::shri::<1>(Self::bitxor(a, b)))
18291829
}
18301830

1831+
/// Per-lane unsigned absolute difference `|a - b|`, without overflow.
1832+
///
1833+
/// Computed as `(a -| b) | (b -| a)` with saturating subtraction: exactly
1834+
/// one of the two saturating subtractions is nonzero (whichever operand is
1835+
/// larger wins), so the `OR` yields `|a - b|` for any unsigned width. On x86
1836+
/// this lowers to the canonical `psubus`/`psubus`/`por` sequence, so no
1837+
/// native override is needed.
1838+
#[conditional]
1839+
fn abs_diff(a: Storage<Self>, b: Storage<Self>) -> Storage<Self> {
1840+
Self::bitor(Self::saturating_sub(a, b), Self::saturating_sub(b, a))
1841+
}
1842+
18311843
// TODO: Interleave bits?
18321844
}
18331845

crates/thermite/src/vector/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1731,6 +1731,13 @@ pub trait UnsignedIntegerVector: IntegerVector<Element: crate::element::Unsigned
17311731
///
17321732
/// Matches x86 `PAVGB`/`PAVGW` and ARM `vrhadd` semantics.
17331733
#[conditional] fn avg(self, other: Self) -> Self;
1734+
1735+
/// Per-lane unsigned absolute difference `|self - other|`, without overflow.
1736+
///
1737+
/// Computed branchlessly as `(self -| other) | (other -| self)` with
1738+
/// saturating subtraction. The per-lane building block of sum-of-absolute-
1739+
/// differences (block matching, motion estimation).
1740+
#[conditional] fn abs_diff(self, other: Self) -> Self;
17341741
}
17351742

17361743
/// Escape hatch tying a [`Vector`] to its specific underlying

crates/thermite/src/vector/vector.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,7 @@ where
625625
#[conditional] fn ilog2p1(self) -> Self {}
626626
#[conditional] fn parity(self) -> Self {}
627627
#[conditional] fn avg(self, other: Self) -> Self {}
628+
#[conditional] fn abs_diff(self, other: Self) -> Self {}
628629
}
629630

630631
#[rustfmt::skip] #[thermite_macros::vector_impl]

crates/thermite/tests/abs_diff.rs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
//! `UnsignedIntegerVector::abs_diff` correctness.
2+
//!
3+
//! `a.abs_diff(b)` is the per-lane unsigned `|a - b|`, computed branchlessly as
4+
//! `(a -| b) | (b -| a)` with saturating subtraction. Verified against Rust's
5+
//! `uN::abs_diff` oracle over all ordered pairs of a probe set (covering equal,
6+
//! a<b, a>b, and the 0/MAX corners). The default composes `saturating_sub`,
7+
//! which is natively overridden per backend, so this is exercised across scalar
8+
//! and x86 v1/v2/v3.
9+
10+
use thermite::backend::scalar::Scalar;
11+
use thermite::prelude::*;
12+
13+
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
14+
use thermite::backend::{x86_v1::X86V1, x86_v2::X86V2, x86_v3::X86V3};
15+
16+
macro_rules! check {
17+
($name:ident, $vty:ty, $elem:ty, $n:literal) => {
18+
#[test]
19+
fn $name() {
20+
type V = $vty;
21+
const N: usize = $n;
22+
23+
let max = <$elem>::MAX;
24+
let probes: [$elem; 8] = [0, 1, 2, 127, 128, max / 2, max - 1, max];
25+
let p = probes.len();
26+
27+
// Every ordered (probe, rotated-probe) pair across the lanes.
28+
for shift in 0..p {
29+
let mut da = [0 as $elem; N];
30+
let mut db = [0 as $elem; N];
31+
for i in 0..N {
32+
da[i] = probes[i % p];
33+
db[i] = probes[(i + shift) % p];
34+
}
35+
let got = V::new(da).abs_diff(V::new(db));
36+
37+
for i in 0..N {
38+
assert_eq!(
39+
got.as_slice()[i],
40+
da[i].abs_diff(db[i]),
41+
"abs_diff a={} b={} lane={}",
42+
da[i], db[i], i
43+
);
44+
}
45+
}
46+
}
47+
};
48+
}
49+
50+
macro_rules! suite {
51+
($modname:ident, $backend:ty) => {
52+
mod $modname {
53+
use super::*;
54+
check!(u8x16, thermite::simd::u8x16<$backend>, u8, 16);
55+
check!(u16x8, thermite::simd::u16x8<$backend>, u16, 8);
56+
check!(u16x16, thermite::simd::u16x16<$backend>, u16, 16);
57+
check!(u32x4, thermite::simd::u32x4<$backend>, u32, 4);
58+
check!(u32x8, thermite::simd::u32x8<$backend>, u32, 8);
59+
check!(u64x2, thermite::simd::u64x2<$backend>, u64, 2);
60+
check!(u64x4, thermite::simd::u64x4<$backend>, u64, 4);
61+
}
62+
};
63+
}
64+
65+
suite!(scalar, Scalar);
66+
67+
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
68+
suite!(v1, X86V1);
69+
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
70+
suite!(v2, X86V2);
71+
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
72+
suite!(v3, X86V3);

0 commit comments

Comments
 (0)