Skip to content

Commit 5f49d4c

Browse files
#239: Bitmask conversion trait
Another approach that fixes #223, as an alternative to #238. This adds the `ToBitMask` trait, which is implemented on a vector for each bitmask type it supports. This includes all unsigned integers with enough bits to contain it. The byte array variant has been separated out for now into #246 and still requires `generic_const_exprs`, but the integer variants no longer require it and can make it to nightly.
2 parents 78a18c3 + 20fa4b7 commit 5f49d4c

File tree

5 files changed

+115
-58
lines changed

5 files changed

+115
-58
lines changed

Diff for: crates/core_simd/src/masks.rs

+4-18
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@
1212
)]
1313
mod mask_impl;
1414

15-
use crate::simd::intrinsics;
16-
use crate::simd::{LaneCount, Simd, SimdElement, SupportedLaneCount};
15+
mod to_bitmask;
16+
pub use to_bitmask::ToBitMask;
17+
18+
use crate::simd::{intrinsics, LaneCount, Simd, SimdElement, SupportedLaneCount};
1719
use core::cmp::Ordering;
1820
use core::{fmt, mem};
1921

@@ -225,22 +227,6 @@ where
225227
}
226228
}
227229

228-
/// Convert this mask to a bitmask, with one bit set per lane.
229-
#[cfg(feature = "generic_const_exprs")]
230-
#[inline]
231-
#[must_use = "method returns a new array and does not mutate the original value"]
232-
pub fn to_bitmask(self) -> [u8; LaneCount::<LANES>::BITMASK_LEN] {
233-
self.0.to_bitmask()
234-
}
235-
236-
/// Convert a bitmask to a mask.
237-
#[cfg(feature = "generic_const_exprs")]
238-
#[inline]
239-
#[must_use = "method returns a new mask and does not mutate the original value"]
240-
pub fn from_bitmask(bitmask: [u8; LaneCount::<LANES>::BITMASK_LEN]) -> Self {
241-
Self(mask_impl::Mask::from_bitmask(bitmask))
242-
}
243-
244230
/// Returns true if any lane is set, or false otherwise.
245231
#[inline]
246232
#[must_use = "method returns a new bool and does not mutate the original value"]

Diff for: crates/core_simd/src/masks/bitmask.rs

+12-10
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#![allow(unused_imports)]
22
use super::MaskElement;
33
use crate::simd::intrinsics;
4-
use crate::simd::{LaneCount, Simd, SupportedLaneCount};
4+
use crate::simd::{LaneCount, Simd, SupportedLaneCount, ToBitMask};
55
use core::marker::PhantomData;
66

77
/// A mask where each lane is represented by a single bit.
@@ -115,20 +115,22 @@ where
115115
unsafe { Self(intrinsics::simd_bitmask(value), PhantomData) }
116116
}
117117

118-
#[cfg(feature = "generic_const_exprs")]
119118
#[inline]
120-
#[must_use = "method returns a new array and does not mutate the original value"]
121-
pub fn to_bitmask(self) -> [u8; LaneCount::<LANES>::BITMASK_LEN] {
122-
// Safety: these are the same type and we are laundering the generic
119+
pub fn to_bitmask_integer<U>(self) -> U
120+
where
121+
super::Mask<T, LANES>: ToBitMask<BitMask = U>,
122+
{
123+
// Safety: these are the same types
123124
unsafe { core::mem::transmute_copy(&self.0) }
124125
}
125126

126-
#[cfg(feature = "generic_const_exprs")]
127127
#[inline]
128-
#[must_use = "method returns a new mask and does not mutate the original value"]
129-
pub fn from_bitmask(bitmask: [u8; LaneCount::<LANES>::BITMASK_LEN]) -> Self {
130-
// Safety: these are the same type and we are laundering the generic
131-
Self(unsafe { core::mem::transmute_copy(&bitmask) }, PhantomData)
128+
pub fn from_bitmask_integer<U>(bitmask: U) -> Self
129+
where
130+
super::Mask<T, LANES>: ToBitMask<BitMask = U>,
131+
{
132+
// Safety: these are the same types
133+
unsafe { Self(core::mem::transmute_copy(&bitmask), PhantomData) }
132134
}
133135

134136
#[inline]

Diff for: crates/core_simd/src/masks/full_masks.rs

+40-28
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
use super::MaskElement;
44
use crate::simd::intrinsics;
5-
use crate::simd::{LaneCount, Simd, SupportedLaneCount};
5+
use crate::simd::{LaneCount, Simd, SupportedLaneCount, ToBitMask};
66

77
#[repr(transparent)]
88
pub struct Mask<T, const LANES: usize>(Simd<T, LANES>)
@@ -66,6 +66,23 @@ where
6666
}
6767
}
6868

69+
// Used for bitmask bit order workaround
70+
pub(crate) trait ReverseBits {
71+
fn reverse_bits(self) -> Self;
72+
}
73+
74+
macro_rules! impl_reverse_bits {
75+
{ $($int:ty),* } => {
76+
$(
77+
impl ReverseBits for $int {
78+
fn reverse_bits(self) -> Self { <$int>::reverse_bits(self) }
79+
}
80+
)*
81+
}
82+
}
83+
84+
impl_reverse_bits! { u8, u16, u32, u64 }
85+
6986
impl<T, const LANES: usize> Mask<T, LANES>
7087
where
7188
T: MaskElement,
@@ -110,41 +127,36 @@ where
110127
unsafe { Mask(intrinsics::simd_cast(self.0)) }
111128
}
112129

113-
#[cfg(feature = "generic_const_exprs")]
114130
#[inline]
115-
#[must_use = "method returns a new array and does not mutate the original value"]
116-
pub fn to_bitmask(self) -> [u8; LaneCount::<LANES>::BITMASK_LEN] {
117-
unsafe {
118-
let mut bitmask: [u8; LaneCount::<LANES>::BITMASK_LEN] =
119-
intrinsics::simd_bitmask(self.0);
120-
121-
// There is a bug where LLVM appears to implement this operation with the wrong
122-
// bit order.
123-
// TODO fix this in a better way
124-
if cfg!(target_endian = "big") {
125-
for x in bitmask.as_mut() {
126-
*x = x.reverse_bits();
127-
}
128-
}
131+
pub(crate) fn to_bitmask_integer<U: ReverseBits>(self) -> U
132+
where
133+
super::Mask<T, LANES>: ToBitMask<BitMask = U>,
134+
{
135+
// Safety: U is required to be the appropriate bitmask type
136+
let bitmask: U = unsafe { intrinsics::simd_bitmask(self.0) };
129137

138+
// LLVM assumes bit order should match endianness
139+
if cfg!(target_endian = "big") {
140+
bitmask.reverse_bits()
141+
} else {
130142
bitmask
131143
}
132144
}
133145

134-
#[cfg(feature = "generic_const_exprs")]
135146
#[inline]
136-
#[must_use = "method returns a new mask and does not mutate the original value"]
137-
pub fn from_bitmask(mut bitmask: [u8; LaneCount::<LANES>::BITMASK_LEN]) -> Self {
138-
unsafe {
139-
// There is a bug where LLVM appears to implement this operation with the wrong
140-
// bit order.
141-
// TODO fix this in a better way
142-
if cfg!(target_endian = "big") {
143-
for x in bitmask.as_mut() {
144-
*x = x.reverse_bits();
145-
}
146-
}
147+
pub(crate) fn from_bitmask_integer<U: ReverseBits>(bitmask: U) -> Self
148+
where
149+
super::Mask<T, LANES>: ToBitMask<BitMask = U>,
150+
{
151+
// LLVM assumes bit order should match endianness
152+
let bitmask = if cfg!(target_endian = "big") {
153+
bitmask.reverse_bits()
154+
} else {
155+
bitmask
156+
};
147157

158+
// Safety: U is required to be the appropriate bitmask type
159+
unsafe {
148160
Self::from_int_unchecked(intrinsics::simd_select_bitmask(
149161
bitmask,
150162
Self::splat(true).to_int(),

Diff for: crates/core_simd/src/masks/to_bitmask.rs

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
use super::{mask_impl, Mask, MaskElement};
2+
use crate::simd::{LaneCount, SupportedLaneCount};
3+
4+
mod sealed {
5+
pub trait Sealed {}
6+
}
7+
pub use sealed::Sealed;
8+
9+
impl<T, const LANES: usize> Sealed for Mask<T, LANES>
10+
where
11+
T: MaskElement,
12+
LaneCount<LANES>: SupportedLaneCount,
13+
{
14+
}
15+
16+
/// Converts masks to and from integer bitmasks.
17+
///
18+
/// Each bit of the bitmask corresponds to a mask lane, starting with the LSB.
19+
///
20+
/// # Safety
21+
/// This trait is `unsafe` and sealed, since the `BitMask` type must match the number of lanes in
22+
/// the mask.
23+
pub unsafe trait ToBitMask: Sealed {
24+
/// The integer bitmask type.
25+
type BitMask;
26+
27+
/// Converts a mask to a bitmask.
28+
fn to_bitmask(self) -> Self::BitMask;
29+
30+
/// Converts a bitmask to a mask.
31+
fn from_bitmask(bitmask: Self::BitMask) -> Self;
32+
}
33+
34+
macro_rules! impl_integer_intrinsic {
35+
{ $(unsafe impl ToBitMask<BitMask=$int:ty> for Mask<_, $lanes:literal>)* } => {
36+
$(
37+
unsafe impl<T: MaskElement> ToBitMask for Mask<T, $lanes> {
38+
type BitMask = $int;
39+
40+
fn to_bitmask(self) -> $int {
41+
self.0.to_bitmask_integer()
42+
}
43+
44+
fn from_bitmask(bitmask: $int) -> Self {
45+
Self(mask_impl::Mask::from_bitmask_integer(bitmask))
46+
}
47+
}
48+
)*
49+
}
50+
}
51+
52+
impl_integer_intrinsic! {
53+
unsafe impl ToBitMask<BitMask=u8> for Mask<_, 8>
54+
unsafe impl ToBitMask<BitMask=u16> for Mask<_, 16>
55+
unsafe impl ToBitMask<BitMask=u32> for Mask<_, 32>
56+
unsafe impl ToBitMask<BitMask=u64> for Mask<_, 64>
57+
}

Diff for: crates/core_simd/tests/masks.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -68,16 +68,16 @@ macro_rules! test_mask_api {
6868
assert_eq!(core_simd::Mask::<$type, 8>::from_int(int), mask);
6969
}
7070

71-
#[cfg(feature = "generic_const_exprs")]
7271
#[test]
7372
fn roundtrip_bitmask_conversion() {
73+
use core_simd::ToBitMask;
7474
let values = [
7575
true, false, false, true, false, false, true, false,
7676
true, true, false, false, false, false, false, true,
7777
];
7878
let mask = core_simd::Mask::<$type, 16>::from_array(values);
7979
let bitmask = mask.to_bitmask();
80-
assert_eq!(bitmask, [0b01001001, 0b10000011]);
80+
assert_eq!(bitmask, 0b1000001101001001);
8181
assert_eq!(core_simd::Mask::<$type, 16>::from_bitmask(bitmask), mask);
8282
}
8383
}

0 commit comments

Comments
 (0)