Skip to content

Commit 3008a4f

Browse files
authored
cmov: extract maskgen32 and maskgen64 in portable backend (#1335)
Extracts some functions in the portable backend that can both be easily tested and easily substituted with some platform-specific idealized assembly for emulating CMOVNZ that everything else can build on top of. Also adds tests in CI for an `armv7-unknown-linux-gnueabi` target we can use for verifying correct behavior on 32-bit platforms.
1 parent 2a514ed commit 3008a4f

File tree

2 files changed

+61
-19
lines changed

2 files changed

+61
-19
lines changed

.github/workflows/cmov.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,11 @@ jobs:
101101
strategy:
102102
matrix:
103103
include:
104+
# ARM32
105+
- target: armv7-unknown-linux-gnueabi
106+
rust: 1.85.0 # MSRV
107+
- target: armv7-unknown-linux-gnueabi
108+
rust: stable # MSRV
104109
# ARM64
105110
- target: aarch64-unknown-linux-gnu
106111
rust: 1.85.0 # MSRV
@@ -130,6 +135,7 @@ jobs:
130135
strategy:
131136
matrix:
132137
target:
138+
- armv7-unknown-linux-gnueabi
133139
- powerpc-unknown-linux-gnu
134140
- s390x-unknown-linux-gnu
135141
- x86_64-unknown-linux-gnu

cmov/src/portable.rs

Lines changed: 55 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,81 +7,80 @@
77
// TODO(tarcieri): more optimized implementation for small integers
88

99
use crate::{Cmov, CmovEq, Condition};
10-
use core::hint::black_box;
1110

1211
/// Bitwise non-zero: returns `1` if `x != 0`, and otherwise returns `0`.
1312
macro_rules! bitnz {
1413
($value:expr, $bits:expr) => {
15-
black_box(($value | $value.wrapping_neg()) >> ($bits - 1))
14+
core::hint::black_box(($value | $value.wrapping_neg()) >> ($bits - 1))
1615
};
1716
}
1817

1918
impl Cmov for u16 {
2019
#[inline]
2120
fn cmovnz(&mut self, value: &Self, condition: Condition) {
22-
let mut tmp = *self as u64;
23-
tmp.cmovnz(&(*value as u64), condition);
21+
let mut tmp = *self as u32;
22+
tmp.cmovnz(&(*value as u32), condition);
2423
*self = tmp as u16;
2524
}
2625

2726
#[inline]
2827
fn cmovz(&mut self, value: &Self, condition: Condition) {
29-
let mut tmp = *self as u64;
30-
tmp.cmovz(&(*value as u64), condition);
28+
let mut tmp = *self as u32;
29+
tmp.cmovz(&(*value as u32), condition);
3130
*self = tmp as u16;
3231
}
3332
}
3433

3534
impl CmovEq for u16 {
3635
#[inline]
3736
fn cmovne(&self, rhs: &Self, input: Condition, output: &mut Condition) {
38-
(*self as u64).cmovne(&(*rhs as u64), input, output);
37+
(*self as u32).cmovne(&(*rhs as u32), input, output);
3938
}
4039

4140
#[inline]
4241
fn cmoveq(&self, rhs: &Self, input: Condition, output: &mut Condition) {
43-
(*self as u64).cmoveq(&(*rhs as u64), input, output);
42+
(*self as u32).cmoveq(&(*rhs as u32), input, output);
4443
}
4544
}
4645

4746
impl Cmov for u32 {
4847
#[inline]
4948
fn cmovnz(&mut self, value: &Self, condition: Condition) {
50-
let mut tmp = *self as u64;
51-
tmp.cmovnz(&(*value as u64), condition);
52-
*self = tmp as u32;
49+
let mask = nzmask32(condition);
50+
*self = (*self & !mask) | (*value & mask);
5351
}
5452

5553
#[inline]
5654
fn cmovz(&mut self, value: &Self, condition: Condition) {
57-
let mut tmp = *self as u64;
58-
tmp.cmovz(&(*value as u64), condition);
59-
*self = tmp as u32;
55+
let mask = nzmask32(condition);
56+
*self = (*self & mask) | (*value & !mask);
6057
}
6158
}
6259

6360
impl CmovEq for u32 {
6461
#[inline]
6562
fn cmovne(&self, rhs: &Self, input: Condition, output: &mut Condition) {
66-
(*self as u64).cmovne(&(*rhs as u64), input, output);
63+
let ne = bitnz!(self ^ rhs, u32::BITS) as u8;
64+
output.cmovnz(&input, ne);
6765
}
6866

6967
#[inline]
7068
fn cmoveq(&self, rhs: &Self, input: Condition, output: &mut Condition) {
71-
(*self as u64).cmoveq(&(*rhs as u64), input, output);
69+
let ne = bitnz!(self ^ rhs, u32::BITS) as u8;
70+
output.cmovnz(&input, ne ^ 1);
7271
}
7372
}
7473

7574
impl Cmov for u64 {
7675
#[inline]
7776
fn cmovnz(&mut self, value: &Self, condition: Condition) {
78-
let mask = (bitnz!(condition, u8::BITS) as u64).wrapping_sub(1);
79-
*self = (*self & mask) | (*value & !mask);
77+
let mask = nzmask64(condition);
78+
*self = (*self & !mask) | (*value & mask);
8079
}
8180

8281
#[inline]
8382
fn cmovz(&mut self, value: &Self, condition: Condition) {
84-
let mask = (1 ^ bitnz!(condition, u8::BITS) as u64).wrapping_sub(1);
83+
let mask = nzmask64(condition);
8584
*self = (*self & mask) | (*value & !mask);
8685
}
8786
}
@@ -99,3 +98,40 @@ impl CmovEq for u64 {
9998
output.cmovnz(&input, ne ^ 1);
10099
}
101100
}
101+
102+
/// Return a [`u32::MAX`] mask if `condition` is non-zero, otherwise return zero for a zero input.
103+
pub fn nzmask32(condition: Condition) -> u32 {
104+
bitnz!(condition as u32, u32::BITS).wrapping_neg()
105+
}
106+
107+
/// Return a [`u64::MAX`] mask if `condition` is non-zero, otherwise return zero for a zero input.
108+
pub fn nzmask64(condition: Condition) -> u64 {
109+
bitnz!(condition as u64, u64::BITS).wrapping_neg()
110+
}
111+
112+
#[cfg(test)]
113+
mod tests {
114+
#[test]
115+
fn bitnz() {
116+
assert_eq!(bitnz!(0u8, u8::BITS), 0);
117+
for i in 1..=u8::MAX {
118+
assert_eq!(bitnz!(i, u8::BITS), 1);
119+
}
120+
}
121+
122+
#[test]
123+
fn nzmask32() {
124+
assert_eq!(super::nzmask32(0), 0);
125+
for i in 1..=u8::MAX {
126+
assert_eq!(super::nzmask32(i), u32::MAX);
127+
}
128+
}
129+
130+
#[test]
131+
fn nzmask64() {
132+
assert_eq!(super::nzmask64(0), 0);
133+
for i in 1..=u8::MAX {
134+
assert_eq!(super::nzmask64(i), u64::MAX);
135+
}
136+
}
137+
}

0 commit comments

Comments
 (0)