Skip to content

Commit b054a27

Browse files
authored
cmov: expand slice impls and tests (#1372)
- Expands `CmovEq` impls to slices of all unsigned core integer types - Expands tests for both `Cmov` and `CmovEq` impls that cover slices of all core integer types - Makes impls of `Cmov` and `CmovEq` for `[T; N]` generic around when the corresponding slice type `[T]` impls the corresponding trait The `CmovEq` impls have not been optimized yet except for `u8`, but that can come later.
1 parent 9985bd6 commit b054a27

File tree

3 files changed

+143
-81
lines changed

3 files changed

+143
-81
lines changed

cmov/src/array.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
//! Trait impls for core arrays.
2+
//!
3+
//! Implemented generically, delegating to the slice impls.
24
35
use crate::{Cmov, CmovEq, Condition};
46

57
/// Optimized implementation for byte arrays which coalesces them into word-sized chunks first,
68
/// then performs [`Cmov`] at the word-level to cut down on the total number of instructions.
7-
impl<const N: usize> Cmov for [u8; N] {
9+
impl<T, const N: usize> Cmov for [T; N]
10+
where
11+
[T]: Cmov,
12+
{
813
#[inline]
914
fn cmovnz(&mut self, value: &Self, condition: Condition) {
1015
self.as_mut_slice().cmovnz(value, condition);
@@ -13,7 +18,11 @@ impl<const N: usize> Cmov for [u8; N] {
1318

1419
/// Optimized implementation for byte arrays which coalesces them into word-sized chunks first,
1520
/// then performs [`CmovEq`] at the word-level to cut down on the total number of instructions.
16-
impl<const N: usize> CmovEq for [u8; N] {
21+
impl<T, const N: usize> CmovEq for [T; N]
22+
where
23+
[T]: CmovEq,
24+
{
25+
#[inline]
1726
fn cmovne(&self, rhs: &Self, input: Condition, output: &mut Condition) {
1827
self.as_slice().cmovne(rhs, input, output);
1928
}

cmov/src/slice.rs

Lines changed: 59 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,25 +22,6 @@ macro_rules! assert_lengths_eq {
2222
};
2323
}
2424

25-
/// Implement [`Cmov`] using a simple loop.
26-
macro_rules! impl_cmov_with_loop {
27-
($int:ty, $doc:expr) => {
28-
#[doc = $doc]
29-
#[doc = "# Panics"]
30-
#[doc = "- if slices have unequal lengths"]
31-
impl Cmov for [$int] {
32-
#[inline]
33-
#[track_caller]
34-
fn cmovnz(&mut self, value: &Self, condition: Condition) {
35-
assert_lengths_eq!(self.len(), value.len());
36-
for (a, b) in self.iter_mut().zip(value.iter()) {
37-
a.cmovnz(b, condition);
38-
}
39-
}
40-
}
41-
};
42-
}
43-
4425
/// Optimized implementation for byte slices which coalesces them into word-sized chunks first,
4526
/// then performs [`Cmov`] at the word-level to cut down on the total number of instructions.
4627
///
@@ -189,6 +170,25 @@ impl Cmov for [u32] {
189170
}
190171
}
191172

173+
/// Implement [`Cmov`] using a simple loop.
174+
macro_rules! impl_cmov_with_loop {
175+
($int:ty, $doc:expr) => {
176+
#[doc = $doc]
177+
#[doc = "# Panics"]
178+
#[doc = "- if slices have unequal lengths"]
179+
impl Cmov for [$int] {
180+
#[inline]
181+
#[track_caller]
182+
fn cmovnz(&mut self, value: &Self, condition: Condition) {
183+
assert_lengths_eq!(self.len(), value.len());
184+
for (a, b) in self.iter_mut().zip(value.iter()) {
185+
a.cmovnz(b, condition);
186+
}
187+
}
188+
}
189+
};
190+
}
191+
192192
impl_cmov_with_loop!(
193193
u64,
194194
"Implementation for `u64` slices where we can just loop."
@@ -229,6 +229,46 @@ impl CmovEq for [u8] {
229229
}
230230
}
231231

232+
/// Implement [`CmovEq`] using a simple loop.
233+
macro_rules! impl_cmoveq_with_loop {
234+
($int:ty, $doc:expr) => {
235+
#[doc = $doc]
236+
impl CmovEq for [$int] {
237+
#[inline]
238+
fn cmovne(&self, rhs: &Self, input: Condition, output: &mut Condition) {
239+
// Short-circuit the comparison if the slices are of different lengths, and set the output
240+
// condition to the input condition.
241+
if self.len() != rhs.len() {
242+
*output = input;
243+
return;
244+
}
245+
246+
for (a, b) in self.iter().zip(rhs.iter()) {
247+
a.cmovne(b, input, output);
248+
}
249+
}
250+
}
251+
};
252+
}
253+
254+
// TODO(tarcieri): optimized coalescing implementations of `CmovEq` for `u16` and `u32`
255+
impl_cmoveq_with_loop!(
256+
u16,
257+
"Implementation for `u16` slices where we can just loop."
258+
);
259+
impl_cmoveq_with_loop!(
260+
u32,
261+
"Implementation for `u32` slices where we can just loop."
262+
);
263+
impl_cmoveq_with_loop!(
264+
u64,
265+
"Implementation for `u64` slices where we can just loop."
266+
);
267+
impl_cmoveq_with_loop!(
268+
u128,
269+
"Implementation for `u128` slices where we can just loop."
270+
);
271+
232272
/// Rust core `[T]::as_chunks` vendored because of its 1.88 MSRV.
233273
/// TODO(tarcieri): use upstream function when we bump MSRV
234274
#[inline]

cmov/tests/core_impls.rs

Lines changed: 73 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -226,76 +226,89 @@ mod arrays {
226226
}
227227

228228
mod slices {
229-
use cmov::{Cmov, CmovEq};
229+
macro_rules! int_slice_test {
230+
($int:ident, $a:expr, $b:expr) => {
231+
mod $int {
232+
use cmov::{Cmov, CmovEq};
233+
234+
const EXAMPLE_A: &[$int] = &[$a, $a, $b];
235+
const EXAMPLE_B: &[$int] = &[$b, $a, $a]; // different contents
236+
const EXAMPLE_C: &[$int] = &[$a, $a]; // different length
237+
238+
#[test]
239+
fn cmovnz_works() {
240+
let mut x: [$int; 3] = [0; 3];
241+
x.as_mut_slice().cmovnz(EXAMPLE_A, 0);
242+
assert_eq!(x, [0; 3]);
243+
244+
for cond in 1..u8::MAX {
245+
let mut x: [$int; 3] = [0; 3];
246+
x.as_mut_slice().cmovnz(EXAMPLE_A, cond);
247+
assert_eq!(x, EXAMPLE_A);
248+
}
249+
}
230250

231-
const EXAMPLE_A: &[u8] = &[1u8, 2, 3];
232-
const EXAMPLE_B: &[u8] = &[1, 2, 4]; // different contents
233-
const EXAMPLE_C: &[u8] = &[1u8, 2]; // different length
251+
#[test]
252+
fn cmovz_works() {
253+
let mut x: [$int; 3] = [0; 3];
254+
x.as_mut_slice().cmovz(EXAMPLE_A, 0);
255+
assert_eq!(x, EXAMPLE_A);
256+
257+
for cond in 1..u8::MAX {
258+
let mut x: [$int; 3] = [0; 3];
259+
x.as_mut_slice().cmovz(EXAMPLE_A, cond);
260+
assert_eq!(x, [0; 3]);
261+
}
262+
}
234263

235-
#[test]
236-
fn u8_cmovnz_works() {
237-
let mut x = [0u8; 3];
238-
x.as_mut_slice().cmovnz(EXAMPLE_A, 0);
239-
assert_eq!(x, [0u8; 3]);
264+
#[test]
265+
#[should_panic]
266+
fn cmovnz_length_mismatch_panics() {
267+
let mut x: [$int; 3] = [0; 3];
268+
x.as_mut_slice().cmovnz(EXAMPLE_C, 1);
269+
}
240270

241-
for cond in 1..u8::MAX {
242-
let mut x = [0u8; 3];
243-
x.as_mut_slice().cmovnz(EXAMPLE_A, cond);
244-
assert_eq!(x, EXAMPLE_A);
245-
}
246-
}
271+
#[test]
272+
fn cmoveq_works() {
273+
let mut o = 0u8;
247274

248-
#[test]
249-
fn u8_cmovz_works() {
250-
let mut x = [0u8; 3];
251-
x.as_mut_slice().cmovz(EXAMPLE_A, 0);
252-
assert_eq!(x, EXAMPLE_A);
275+
// Same slices.
276+
EXAMPLE_A.cmoveq(EXAMPLE_A, 43, &mut o);
277+
assert_eq!(o, 43);
253278

254-
for cond in 1..u8::MAX {
255-
let mut x = [0u8; 3];
256-
x.as_mut_slice().cmovz(EXAMPLE_A, cond);
257-
assert_eq!(x, [0u8; 3]);
258-
}
259-
}
279+
// Different contents.
280+
EXAMPLE_A.cmoveq(EXAMPLE_B, 45, &mut o);
281+
assert_ne!(o, 45);
260282

261-
#[test]
262-
#[should_panic]
263-
fn u8_cmovnz_length_mismatch_panics() {
264-
let mut x = [0u8; 3];
265-
x.as_mut_slice().cmovnz(EXAMPLE_C, 1);
266-
}
283+
// Different lengths.
284+
EXAMPLE_A.cmoveq(EXAMPLE_C, 44, &mut o);
285+
assert_ne!(o, 44);
286+
}
267287

268-
#[test]
269-
fn u8_cmoveq_works() {
270-
let mut o = 0u8;
288+
#[test]
289+
fn cmovne_works() {
290+
let mut o = 0u8;
271291

272-
// Same slices.
273-
EXAMPLE_A.cmoveq(EXAMPLE_A, 43, &mut o);
274-
assert_eq!(o, 43);
292+
// Same slices.
293+
EXAMPLE_A.cmovne(EXAMPLE_A, 43, &mut o);
294+
assert_ne!(o, 43);
275295

276-
// Different contents.
277-
EXAMPLE_A.cmoveq(EXAMPLE_B, 45, &mut o);
278-
assert_ne!(o, 45);
296+
// Different contents.
297+
EXAMPLE_A.cmovne(EXAMPLE_B, 45, &mut o);
298+
assert_eq!(o, 45);
279299

280-
// Different lengths.
281-
EXAMPLE_A.cmoveq(EXAMPLE_C, 44, &mut o);
282-
assert_ne!(o, 44);
300+
// Different lengths.
301+
EXAMPLE_A.cmovne(EXAMPLE_C, 44, &mut o);
302+
assert_eq!(o, 44);
303+
}
304+
}
305+
};
283306
}
284307

285-
#[test]
286-
fn u8_cmovne_works() {
287-
let mut o = 0u8;
288-
289-
// Same slices.
290-
EXAMPLE_A.cmovne(EXAMPLE_A, 43, &mut o);
291-
assert_ne!(o, 43);
292-
293-
// Different contents.
294-
EXAMPLE_A.cmovne(EXAMPLE_B, 45, &mut o);
295-
assert_eq!(o, 45);
296-
297-
// Different lengths.
298-
EXAMPLE_A.cmovne(EXAMPLE_C, 44, &mut o);
299-
assert_eq!(o, 44);
300-
}
308+
// TODO: int_slice_test!(i8, i8::MIN, i8::MAX);
309+
int_slice_test!(u8, 7, u8::MAX);
310+
int_slice_test!(u16, 11, u16::MAX);
311+
int_slice_test!(u32, 13, u32::MAX);
312+
int_slice_test!(u64, 17, u64::MAX);
313+
int_slice_test!(u128, 23, u128::MAX);
301314
}

0 commit comments

Comments
 (0)