Repository: bitshifter/glam-rs (rust)

Issue:
Boolean vector type aliases are a portability hazard (BVec3/BVec3A, and BVec4/BVec4A)
Sorry for the dramatic title - it's mostly an ergonomics issue. :-)

Currently, `BVec4A` and `BVec3A` are type aliases for `BVec3` and `BVec4` on platforms without explicit SIMD support (most prominently AArch64).

This means they can be used interchangeably on those platforms, but not on x86. Someone writing code on an AArch64 platform could assume that the code will also compile on x86.

Possible solutions:

- Define `BVec3/4` and `BVec3A/4A` as separate types regardless of SIMD support.
- Deprecate `BVec3` and `BVec4`. Personally I'm struggling to see the use case for them (in contrast to `Vec3`), but I could be missing something.

Cheers!

Pull request description:
Add scalar `u32` implementation of `BVec3A` and `BVec4` when SIMD is not available.
Fixes #306.

There is a 13% performance penalty from this change however in practice glam intends to implement stable SIMD architectures and nightly users can use the core-simd implementation, so this shouldn't be a problem.

Patch:
diff --git a/codegen/src/outputs.rs b/codegen/src/outputs.rs
index d41be8c..368f0e5 100644
--- a/codegen/src/outputs.rs
+++ b/codegen/src/outputs.rs
@@ -106,23 +106,32 @@ impl ContextBuilder {
         Self::new_taffinen(3, "f64")
     }
 
-    pub fn new_bvecn(dim: u32) -> Self {
+    pub fn new_bvecn(dim: u32, scalar_t: &str) -> Self {
         ContextBuilder::new()
             .with_template("vec_mask.rs.tera")
+            .with_scalar_t(scalar_t)
             .target_scalar()
             .with_dimension(dim)
     }
 
     pub fn new_bvec2() -> Self {
-        Self::new_bvecn(2)
+        Self::new_bvecn(2, "bool")
     }
 
     pub fn new_bvec3() -> Self {
-        Self::new_bvecn(3)
+        Self::new_bvecn(3, "bool")
     }
 
     pub fn new_bvec4() -> Self {
-        Self::new_bvecn(4)
+        Self::new_bvecn(4, "bool")
+    }
+
+    pub fn new_bvec3a() -> Self {
+        Self::new_bvecn(3, "u32")
+    }
+
+    pub fn new_bvec4a() -> Self {
+        Self::new_bvecn(4, "u32")
     }
 
     pub fn new_vecn(dim: u32) -> Self {
@@ -403,29 +412,37 @@ pub fn build_output_pairs() -> HashMap<&'static str, tera::Context> {
         ("src/bool/bvec2.rs", ContextBuilder::new_bvec2().build()),
         ("src/bool/bvec3.rs", ContextBuilder::new_bvec3().build()),
         ("src/bool/bvec4.rs", ContextBuilder::new_bvec4().build()),
+        (
+            "src/bool/scalar/bvec3a.rs",
+            ContextBuilder::new_bvec3a().build(),
+        ),
         (
             "src/bool/sse2/bvec3a.rs",
-            ContextBuilder::new_bvec3().target_sse2().build(),
+            ContextBuilder::new_bvec3a().target_sse2().build(),
         ),
         (
             "src/bool/wasm32/bvec3a.rs",
-            ContextBuilder::new_bvec3().target_wasm32().build(),
+            ContextBuilder::new_bvec3a().target_wasm32().build(),
         ),
         (
             "src/bool/coresimd/bvec3a.rs",
-            ContextBuilder::new_bvec3().target_coresimd().build(),
+            ContextBuilder::new_bvec3a().target_coresimd().build(),
+        ),
+        (
+            "src/bool/scalar/bvec4a.rs",
+            ContextBuilder::new_bvec4a().build(),
         ),
         (
             "src/bool/sse2/bvec4a.rs",
-            ContextBuilder::new_bvec4().target_sse2().build(),
+            ContextBuilder::new_bvec4a().target_sse2().build(),
         ),
         (
             "src/bool/wasm32/bvec4a.rs",
-            ContextBuilder::new_bvec4().target_wasm32().build(),
+            ContextBuilder::new_bvec4a().target_wasm32().build(),
         ),
         (
             "src/bool/coresimd/bvec4a.rs",
-            ContextBuilder::new_bvec4().target_coresimd().build(),
+            ContextBuilder::new_bvec4a().target_coresimd().build(),
         ),
         ("src/f32/vec2.rs", ContextBuilder::new_vec2().build()),
         ("src/f32/vec3.rs", ContextBuilder::new_vec3().build()),
diff --git a/codegen/templates/vec_mask.rs.tera b/codegen/templates/vec_mask.rs.tera
index f6a02aa..336d1b8 100644
--- a/codegen/templates/vec_mask.rs.tera
+++ b/codegen/templates/vec_mask.rs.tera
@@ -1,13 +1,13 @@
 // Generated from {{template_path}} template. Edit the template, not the generated file.
 
-{% if is_scalar %}
+{% if scalar_t == "bool" %}
     {% set self_t = "BVec" ~ dim %}
-    {% set scalar_t = "bool" %}
     {% set align = 1 %}
+    {% set is_bool = true %}
 {% else %}
     {% set self_t = "BVec" ~ dim ~ "A" %}
-    {% set scalar_t = "u32" %}
     {% set align = 16 %}
+    {% set is_u32 = true %}
     {% if is_sse2 %}
         {% set simd_t = "__m128" %}
     {% elif is_wasm32 %}
@@ -41,8 +41,10 @@ union UnionCast {
 }
 {% endif %}
 
-{% if is_scalar %}
-/// A {{ dim }}-dimensional boolean vector.
+{%- if is_scalar and is_bool %}
+/// A {{ dim }}-dimensional `bool` vector mask.
+{%- elif is_scalar and is_u32 %}
+/// A {{ dim }}-dimensional `u32` vector mask.
 {%- else %}
 /// A {{ dim }}-dimensional SIMD vector mask.
 ///
@@ -51,11 +53,11 @@ union UnionCast {
 {%- endif %}
 #[derive(Clone, Copy)]
 {%- if is_scalar %}
-#[repr(C)]
+#[repr(C, align({{ align }}))]
 pub struct {{ self_t }}
 {
 {% for c in components %}
-    pub {{ c }}: bool,
+    pub {{ c }}: {{ scalar_t }},
 {%- endfor %}
 }
 {%- else %}
@@ -80,12 +82,18 @@ impl {{ self_t }} {
             {{ c }}: bool,
         {% endfor %}
     ) -> Self {
-        {% if is_scalar %}
+        {% if is_scalar and is_bool %}
             Self {
                 {% for c in components %}
                     {{ c }},
                 {%- endfor %}
             }
+        {% elif is_scalar and is_u32 %}
+            Self {
+                {% for c in components %}
+                    {{ c }}: MASK[{{ c }} as usize],
+                {%- endfor %}
+            }
         {% elif is_sse2 or is_coresimd %}
             unsafe {
                 UnionCast { a: [
@@ -119,7 +127,7 @@ impl {{ self_t }} {
     /// into the first lowest bit, element `y` into the second, etc.
     #[inline]
     pub fn bitmask(self) -> u32 {
-        {% if is_scalar %}
+        {% if is_scalar and is_bool %}
             {% for c in components %}
                 {% if loop.first %}
                     (self.{{ c }} as u32) |
@@ -127,6 +135,14 @@ impl {{ self_t }} {
                     (self.{{ c }} as u32) << {{ loop.index0 }} {% if not loop.last %} | {% endif %}
                 {% endif %}
             {% endfor %}
+        {% elif is_scalar and is_u32 %}
+            {% for c in components %}
+                {% if loop.first %}
+                    (self.{{ c }} & 0x1) |
+                {% else %}
+                    (self.{{ c }} & 0x1) << {{ loop.index0 }} {% if not loop.last %} | {% endif %}
+                {% endif %}
+            {% endfor %}
         {% elif is_sse2 %}
             {% if dim == 3 %}
                 unsafe { (_mm_movemask_ps(self.0) as u32) & 0x7 }
@@ -151,10 +167,16 @@ impl {{ self_t }} {
     /// Returns true if any of the elements are true, false otherwise.
     #[inline]
     pub fn any(self) -> bool {
-        {% if is_scalar %}
+        {% if is_scalar and is_bool %}
             {% for c in components %}
                 self.{{ c }} {% if not loop.last %} || {% endif %}
             {%- endfor %}
+        {% elif is_scalar and is_u32 %}
+            ((
+                {% for c in components %}
+                    self.{{ c }} {% if not loop.last %} | {% endif %}
+                {%- endfor %}
+            ) & 0x1) != 0
         {% else %}
             self.bitmask() != 0
         {% endif %}
@@ -163,10 +185,16 @@ impl {{ self_t }} {
     /// Returns true if all the elements are true, false otherwise.
     #[inline]
     pub fn all(self) -> bool {
-        {% if is_scalar %}
+        {% if is_scalar and is_bool %}
             {% for c in components %}
                 self.{{ c }} {% if not loop.last %} && {% endif %}
             {%- endfor %}
+        {% elif is_scalar and is_u32 %}
+            ((
+                {% for c in components %}
+                    self.{{ c }} {% if not loop.last %} & {% endif %}
+                {%- endfor %}
+            ) & 0x1) != 0
         {% else %}
             {% if dim == 3 %}
                 self.bitmask() == 0x7
@@ -178,12 +206,18 @@ impl {{ self_t }} {
 
     #[inline]
     fn into_bool_array(self) -> [bool; {{ dim }}] {
-        {% if is_scalar %}
+        {% if is_scalar and is_bool %}
             [
                 {% for c in components %}
                     self.{{ c }},
                 {%- endfor %}
             ]
+        {% elif is_scalar and is_u32 %}
+            [
+                {% for c in components %}
+                    (self.{{ c }} & 0x1) != 0,
+                {%- endfor %}
+            ]
         {% else %}
             {% set bits = [1, 2, 4, 8] | slice(end = dim) %}
             let bitmask = self.bitmask();
@@ -197,12 +231,18 @@ impl {{ self_t }} {
 
     #[inline]
     fn into_u32_array(self) -> [u32; {{ dim }}] {
-        {% if is_scalar %}
+        {% if is_scalar and is_bool %}
             [
                 {% for c in components %}
                     MASK[self.{{ c }} as usize],
                 {%- endfor %}
             ]
+        {% elif is_scalar and is_u32 %}
+            [
+                {% for c in components %}
+                    self.{{ c }},
+                {%- endfor %}
+            ]
         {% else %}
             let bitmask = self.bitmask();
             [
diff --git a/src/bool.rs b/src/bool.rs
index cfe194a..889a0ed 100644
--- a/src/bool.rs
+++ b/src/bool.rs
@@ -17,6 +17,14 @@ mod sse2;
 ))]
 mod wasm32;
 
+#[cfg(not(any(
+    feature = "scalar-math",
+    feature = "core-simd",
+    target_feature = "sse2",
+    target_feature = "simd128"
+),))]
+mod scalar;
+
 pub use bvec2::BVec2;
 pub use bvec3::BVec3;
 pub use bvec4::BVec4;
@@ -48,25 +56,21 @@ pub use coresimd::bvec3a::BVec3A;
 #[cfg(all(feature = "core-simd", not(feature = "scalar-math")))]
 pub use coresimd::bvec4a::BVec4A;
 
-#[cfg(any(
-    not(any(
-        feature = "core-simd",
-        target_feature = "sse2",
-        target_feature = "simd128"
-    )),
-    feature = "scalar-math"
-))]
-pub type BVec3A = BVec3;
+#[cfg(not(any(
+    feature = "scalar-math",
+    feature = "core-simd",
+    target_feature = "sse2",
+    target_feature = "simd128"
+),))]
+pub use scalar::bvec3a::BVec3A;
 
-#[cfg(any(
-    not(any(
-        feature = "core-simd",
-        target_feature = "sse2",
-        target_feature = "simd128"
-    )),
-    feature = "scalar-math"
-))]
-pub type BVec4A = BVec4;
+#[cfg(not(any(
+    feature = "scalar-math",
+    feature = "core-simd",
+    target_feature = "sse2",
+    target_feature = "simd128"
+),))]
+pub use scalar::bvec4a::BVec4A;
 
 mod const_test_bvec2 {
     const_assert_eq!(1, core::mem::align_of::<super::BVec2>());
@@ -83,19 +87,13 @@ mod const_test_bvec4 {
     const_assert_eq!(4, core::mem::size_of::<super::BVec4>());
 }
 
-#[cfg(all(
-    any(target_feature = "sse2", target_feature = "simd128"),
-    not(feature = "scalar-math")
-))]
+#[cfg(not(feature = "scalar-math"))]
 mod const_test_bvec3a {
     const_assert_eq!(16, core::mem::align_of::<super::BVec3A>());
     const_assert_eq!(16, core::mem::size_of::<super::BVec3A>());
 }
 
-#[cfg(all(
-    any(target_feature = "sse2", target_feature = "simd128"),
-    not(feature = "scalar-math")
-))]
+#[cfg(not(feature = "scalar-math"))]
 mod const_test_bvec4a {
     const_assert_eq!(16, core::mem::align_of::<super::BVec4A>());
     const_assert_eq!(16, core::mem::size_of::<super::BVec4A>());
diff --git a/src/bool/bvec2.rs b/src/bool/bvec2.rs
index b11df5c..735a483 100644
--- a/src/bool/bvec2.rs
+++ b/src/bool/bvec2.rs
@@ -4,9 +4,9 @@
 use core::fmt;
 use core::{hash, ops::*};
 
-/// A 2-dimensional boolean vector.
+/// A 2-dimensional `bool` vector mask.
 #[derive(Clone, Copy)]
-#[repr(C)]
+#[repr(C, align(1))]
 pub struct BVec2 {
     pub x: bool,
     pub y: bool,
diff --git a/src/bool/bvec3.rs b/src/bool/bvec3.rs
index 077abc3..f415a30 100644
--- a/src/bool/bvec3.rs
+++ b/src/bool/bvec3.rs
@@ -4,9 +4,9 @@
 use core::fmt;
 use core::{hash, ops::*};
 
-/// A 3-dimensional boolean vector.
+/// A 3-dimensional `bool` vector mask.
 #[derive(Clone, Copy)]
-#[repr(C)]
+#[repr(C, align(1))]
 pub struct BVec3 {
     pub x: bool,
     pub y: bool,
diff --git a/src/bool/bvec4.rs b/src/bool/bvec4.rs
index ea8ebdd..b401cf1 100644
--- a/src/bool/bvec4.rs
+++ b/src/bool/bvec4.rs
@@ -4,9 +4,9 @@
 use core::fmt;
 use core::{hash, ops::*};
 
-/// A 4-dimensional boolean vector.
+/// A 4-dimensional `bool` vector mask.
 #[derive(Clone, Copy)]
-#[repr(C)]
+#[repr(C, align(1))]
 pub struct BVec4 {
     pub x: bool,
     pub y: bool,
diff --git a/src/bool/scalar.rs b/src/bool/scalar.rs
new file mode 100644
index 0000000..9a9a329
--- /dev/null
+++ b/src/bool/scalar.rs
@@ -0,0 +1,2 @@
+pub mod bvec3a;
+pub mod bvec4a;
diff --git a/src/bool/scalar/bvec3a.rs b/src/bool/scalar/bvec3a.rs
new file mode 100644
index 0000000..44acd08
--- /dev/null
+++ b/src/bool/scalar/bvec3a.rs
@@ -0,0 +1,194 @@
+// Generated from vec_mask.rs.tera template. Edit the template, not the generated file.
+
+#[cfg(not(target_arch = "spirv"))]
+use core::fmt;
+use core::{hash, ops::*};
+
+/// A 3-dimensional `u32` vector mask.
+#[derive(Clone, Copy)]
+#[repr(C, align(16))]
+pub struct BVec3A {
+    pub x: u32,
+    pub y: u32,
+    pub z: u32,
+}
+
+const MASK: [u32; 2] = [0, 0xff_ff_ff_ff];
+
+const FALSE: BVec3A = BVec3A::new(false, false, false);
+
+impl BVec3A {
+    /// Creates a new vector mask.
+    #[inline(always)]
+    pub const fn new(x: bool, y: bool, z: bool) -> Self {
+        Self {
+            x: MASK[x as usize],
+            y: MASK[y as usize],
+            z: MASK[z as usize],
+        }
+    }
+
+    /// Returns a bitmask with the lowest two bits set from the elements of `self`.
+    ///
+    /// A true element results in a `1` bit and a false element in a `0` bit.  Element `x` goes
+    /// into the first lowest bit, element `y` into the second, etc.
+    #[inline]
+    pub fn bitmask(self) -> u32 {
+        (self.x & 0x1) | (self.y & 0x1) << 1 | (self.z & 0x1) << 2
+    }
+
+    /// Returns true if any of the elements are true, false otherwise.
+    #[inline]
+    pub fn any(self) -> bool {
+        ((self.x | self.y | self.z) & 0x1) != 0
+    }
+
+    /// Returns true if all the elements are true, false otherwise.
+    #[inline]
+    pub fn all(self) -> bool {
+        ((self.x & self.y & self.z) & 0x1) != 0
+    }
+
+    #[inline]
+    fn into_bool_array(self) -> [bool; 3] {
+        [
+            (self.x & 0x1) != 0,
+            (self.y & 0x1) != 0,
+            (self.z & 0x1) != 0,
+        ]
+    }
+
+    #[inline]
+    fn into_u32_array(self) -> [u32; 3] {
+        [self.x, self.y, self.z]
+    }
+}
+
+impl Default for BVec3A {
+    #[inline]
+    fn default() -> Self {
+        FALSE
+    }
+}
+
+impl PartialEq for BVec3A {
+    #[inline]
+    fn eq(&self, rhs: &Self) -> bool {
+        self.bitmask().eq(&rhs.bitmask())
+    }
+}
+
+impl Eq for BVec3A {}
+
+impl hash::Hash for BVec3A {
+    #[inline]
+    fn hash<H: hash::Hasher>(&self, state: &mut H) {
+        self.bitmask().hash(state);
+    }
+}
+
+impl BitAnd for BVec3A {
+    type Output = Self;
+    #[inline]
+    fn bitand(self, rhs: Self) -> Self {
+        Self {
+            x: self.x & rhs.x,
+            y: self.y & rhs.y,
+            z: self.z & rhs.z,
+        }
+    }
+}
+
+impl BitAndAssign for BVec3A {
+    #[inline]
+    fn bitand_assign(&mut self, rhs: Self) {
+        *self = self.bitand(rhs);
+    }
+}
+
+impl BitOr for BVec3A {
+    type Output = Self;
+    #[inline]
+    fn bitor(self, rhs: Self) -> Self {
+        Self {
+            x: self.x | rhs.x,
+            y: self.y | rhs.y,
+            z: self.z | rhs.z,
+        }
+    }
+}
+
+impl BitOrAssign for BVec3A {
+    #[inline]
+    fn bitor_assign(&mut self, rhs: Self) {
+        *self = self.bitor(rhs);
+    }
+}
+
+impl BitXor for BVec3A {
+    type Output = Self;
+    #[inline]
+    fn bitxor(self, rhs: Self) -> Self {
+        Self {
+            x: self.x ^ rhs.x,
+            y: self.y ^ rhs.y,
+            z: self.z ^ rhs.z,
+        }
+    }
+}
+
+impl BitXorAssign for BVec3A {
+    #[inline]
+    fn bitxor_assign(&mut self, rhs: Self) {
+        *self = self.bitxor(rhs);
+    }
+}
+
+impl Not for BVec3A {
+    type Output = Self;
+    #[inline]
+    fn not(self) -> Self {
+        Self {
+            x: !self.x,
+            y: !self.y,
+            z: !self.z,
+        }
+    }
+}
+
+#[cfg(not(target_arch = "spirv"))]
+impl fmt::Debug for BVec3A {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        let arr = self.into_u32_array();
+        write!(
+            f,
+            "{}({:#x}, {:#x}, {:#x})",
+            stringify!(BVec3A),
+            arr[0],
+            arr[1],
+            arr[2]
+        )
+    }
+}
+
+#[cfg(not(target_arch = "spirv"))]
+impl fmt::Display for BVec3A {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        let arr = self.into_bool_array();
+        write!(f, "[{}, {}, {}]", arr[0], arr[1], arr[2])
+    }
+}
+
+impl From<BVec3A> for [bool; 3] {
+    #[inline]
+    fn from(mask: BVec3A) -> Self {
+        mask.into_bool_array()
+    }
+}
+
+impl From<BVec3A> for [u32; 3] {
+    #[inline]
+    fn from(mask: BVec3A) -> Self {
+        mask.into_u32_array()
+    }
+}
diff --git a/src/bool/scalar/bvec4a.rs b/src/bool/scalar/bvec4a.rs
new file mode 100644
index 0000000..7b4a5a0
--- /dev/null
+++ b/src/bool/scalar/bvec4a.rs
@@ -0,0 +1,202 @@
+// Generated from vec_mask.rs.tera template. Edit the template, not the generated file.
+
+#[cfg(not(target_arch = "spirv"))]
+use core::fmt;
+use core::{hash, ops::*};
+
+/// A 4-dimensional `u32` vector mask.
+#[derive(Clone, Copy)]
+#[repr(C, align(16))]
+pub struct BVec4A {
+    pub x: u32,
+    pub y: u32,
+    pub z: u32,
+    pub w: u32,
+}
+
+const MASK: [u32; 2] = [0, 0xff_ff_ff_ff];
+
+const FALSE: BVec4A = BVec4A::new(false, false, false, false);
+
+impl BVec4A {
+    /// Creates a new vector mask.
+    #[inline(always)]
+    pub const fn new(x: bool, y: bool, z: bool, w: bool) -> Self {
+        Self {
+            x: MASK[x as usize],
+            y: MASK[y as usize],
+            z: MASK[z as usize],
+            w: MASK[w as usize],
+        }
+    }
+
+    /// Returns a bitmask with the lowest two bits set from the elements of `self`.
+    ///
+    /// A true element results in a `1` bit and a false element in a `0` bit.  Element `x` goes
+    /// into the first lowest bit, element `y` into the second, etc.
+    #[inline]
+    pub fn bitmask(self) -> u32 {
+        (self.x & 0x1) | (self.y & 0x1) << 1 | (self.z & 0x1) << 2 | (self.w & 0x1) << 3
+    }
+
+    /// Returns true if any of the elements are true, false otherwise.
+    #[inline]
+    pub fn any(self) -> bool {
+        ((self.x | self.y | self.z | self.w) & 0x1) != 0
+    }
+
+    /// Returns true if all the elements are true, false otherwise.
+    #[inline]
+    pub fn all(self) -> bool {
+        ((self.x & self.y & self.z & self.w) & 0x1) != 0
+    }
+
+    #[inline]
+    fn into_bool_array(self) -> [bool; 4] {
+        [
+            (self.x & 0x1) != 0,
+            (self.y & 0x1) != 0,
+            (self.z & 0x1) != 0,
+            (self.w & 0x1) != 0,
+        ]
+    }
+
+    #[inline]
+    fn into_u32_array(self) -> [u32; 4] {
+        [self.x, self.y, self.z, self.w]
+    }
+}
+
+impl Default for BVec4A {
+    #[inline]
+    fn default() -> Self {
+        FALSE
+    }
+}
+
+impl PartialEq for BVec4A {
+    #[inline]
+    fn eq(&self, rhs: &Self) -> bool {
+        self.bitmask().eq(&rhs.bitmask())
+    }
+}
+
+impl Eq for BVec4A {}
+
+impl hash::Hash for BVec4A {
+    #[inline]
+    fn hash<H: hash::Hasher>(&self, state: &mut H) {
+        self.bitmask().hash(state);
+    }
+}
+
+impl BitAnd for BVec4A {
+    type Output = Self;
+    #[inline]
+    fn bitand(self, rhs: Self) -> Self {
+        Self {
+            x: self.x & rhs.x,
+            y: self.y & rhs.y,
+            z: self.z & rhs.z,
+            w: self.w & rhs.w,
+        }
+    }
+}
+
+impl BitAndAssign for BVec4A {
+    #[inline]
+    fn bitand_assign(&mut self, rhs: Self) {
+        *self = self.bitand(rhs);
+    }
+}
+
+impl BitOr for BVec4A {
+    type Output = Self;
+    #[inline]
+    fn bitor(self, rhs: Self) -> Self {
+        Self {
+            x: self.x | rhs.x,
+            y: self.y | rhs.y,
+            z: self.z | rhs.z,
+            w: self.w | rhs.w,
+        }
+    }
+}
+
+impl BitOrAssign for BVec4A {
+    #[inline]
+    fn bitor_assign(&mut self, rhs: Self) {
+        *self = self.bitor(rhs);
+    }
+}
+
+impl BitXor for BVec4A {
+    type Output = Self;
+    #[inline]
+    fn bitxor(self, rhs: Self) -> Self {
+        Self {
+            x: self.x ^ rhs.x,
+            y: self.y ^ rhs.y,
+            z: self.z ^ rhs.z,
+            w: self.w ^ rhs.w,
+        }
+    }
+}
+
+impl BitXorAssign for BVec4A {
+    #[inline]
+    fn bitxor_assign(&mut self, rhs: Self) {
+        *self = self.bitxor(rhs);
+    }
+}
+
+impl Not for BVec4A {
+    type Output = Self;
+    #[inline]
+    fn not(self) -> Self {
+        Self {
+            x: !self.x,
+            y: !self.y,
+            z: !self.z,
+            w: !self.w,
+        }
+    }
+}
+
+#[cfg(not(target_arch = "spirv"))]
+impl fmt::Debug for BVec4A {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        let arr = self.into_u32_array();
+        write!(
+            f,
+            "{}({:#x}, {:#x}, {:#x}, {:#x})",
+            stringify!(BVec4A),
+            arr[0],
+            arr[1],
+            arr[2],
+            arr[3]
+        )
+    }
+}
+
+#[cfg(not(target_arch = "spirv"))]
+impl fmt::Display for BVec4A {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        let arr = self.into_bool_array();
+        write!(f, "[{}, {}, {}, {}]", arr[0], arr[1], arr[2], arr[3])
+    }
+}
+
+impl From<BVec4A> for [bool; 4] {
+    #[inline]
+    fn from(mask: BVec4A) -> Self {
+        mask.into_bool_array()
+    }
+}
+
+impl From<BVec4A> for [u32; 4] {
+    #[inline]
+    fn from(mask: BVec4A) -> Self {
+        mask.into_u32_array()
+    }
+}
diff --git a/src/deref.rs b/src/deref.rs
index 1f5890f..34361f4 100644
--- a/src/deref.rs
+++ b/src/deref.rs
@@ -1,4 +1,4 @@
-#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd)]
 #[cfg_attr(target_arch = "spirv", repr(simd))]
 #[cfg_attr(not(target_arch = "spirv"), repr(C))]
 pub struct XY<T> {
@@ -6,7 +6,7 @@ pub struct XY<T> {
     pub y: T,
 }
 
-#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd)]
 #[cfg_attr(target_arch = "spirv", repr(simd))]
 #[cfg_attr(not(target_arch = "spirv"), repr(C))]
 pub struct Vec3<T> {
@@ -15,7 +15,7 @@ pub struct Vec3<T> {
     pub z: T,
 }
 
-#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd)]
 #[cfg_attr(target_arch = "spirv", repr(simd))]
 #[cfg_attr(not(target_arch = "spirv"), repr(C))]
 pub struct Vec4<T> {
@@ -25,14 +25,14 @@ pub struct Vec4<T> {
     pub w: T,
 }
 
-#[derive(Clone, Copy, Default, PartialEq, PartialOrd)]
+#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd)]
 #[cfg_attr(not(target_arch = "spirv"), repr(C))]
 pub struct Cols2<V> {
     pub x_axis: V,
     pub y_axis: V,
 }
 
-#[derive(Clone, Copy, Default, PartialEq, PartialOrd)]
+#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd)]
 #[cfg_attr(not(target_arch = "spirv"), repr(C))]
 pub struct Cols3<V> {
     pub x_axis: V,
@@ -40,7 +40,7 @@ pub struct Cols3<V> {
     pub z_axis: V,
 }
 
-#[derive(Clone, Copy, Default, PartialEq, PartialOrd)]
+#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd)]
 #[cfg_attr(not(target_arch = "spirv"), repr(C))]
 pub struct Cols4<V> {
     pub x_axis: V,


Test patch:
diff --git a/tests/vec3.rs b/tests/vec3.rs
index f9c2096..309ac3a 100644
--- a/tests/vec3.rs
+++ b/tests/vec3.rs
@@ -1055,21 +1055,27 @@ mod vec3 {
 }
 
 mod vec3a {
+    #[cfg(any(
+        not(any(target_feature = "sse2", target_feature = "simd128")),
+        feature = "scalar-math"
+    ))]
+    use glam::BVec3;
+    #[cfg(not(feature = "scalar-math"))]
+    use glam::BVec3A;
     #[allow(deprecated)]
-    use glam::{const_vec3a, vec3a, BVec3, BVec3A, Vec3A, Vec4};
+    use glam::{const_vec3a, vec3a, Vec3A, Vec4};
 
     glam_test!(test_align, {
         use std::mem;
         assert_eq!(16, mem::size_of::<Vec3A>());
         assert_eq!(16, mem::align_of::<Vec3A>());
-        if cfg!(all(
-            any(target_feature = "sse2", target_feature = "simd128"),
-            not(feature = "scalar-math")
-        )) {
+        #[cfg(not(feature = "scalar-math"))]
+        {
             assert_eq!(16, mem::size_of::<BVec3A>());
             assert_eq!(16, mem::align_of::<BVec3A>());
-        } else {
-            // BVec3A aliases BVec3
+        }
+        #[cfg(feature = "scalar-math")]
+        {
             assert_eq!(3, mem::size_of::<BVec3>());
             assert_eq!(1, mem::align_of::<BVec3>());
         }
diff --git a/tests/vec4.rs b/tests/vec4.rs
index de68a2f..18c00e1 100644
--- a/tests/vec4.rs
+++ b/tests/vec4.rs
@@ -1059,8 +1059,15 @@ macro_rules! impl_vec4_bit_op_tests {
     };
 }
 mod vec4 {
+    #[cfg(any(
+        not(any(target_feature = "sse2", target_feature = "simd128")),
+        feature = "scalar-math"
+    ))]
+    use glam::BVec4;
+    #[cfg(not(feature = "scalar-math"))]
+    use glam::BVec4A;
     #[allow(deprecated)]
-    use glam::{const_vec4, vec4, BVec4, BVec4A, Vec2, Vec3, Vec4};
+    use glam::{const_vec4, vec4, Vec2, Vec3, Vec4};
 
     glam_test!(test_align, {
         use std::mem;
@@ -1070,13 +1077,13 @@ mod vec4 {
         } else {
             assert_eq!(4, mem::align_of::<Vec4>());
         }
-        if cfg!(all(
-            any(target_feature = "sse2", target_feature = "simd128"),
-            not(feature = "scalar-math")
-        )) {
+        #[cfg(not(feature = "scalar-math"))]
+        {
             assert_eq!(16, mem::size_of::<BVec4A>());
             assert_eq!(16, mem::align_of::<BVec4A>());
-        } else {
+        }
+        #[cfg(feature = "scalar-math")]
+        {
             assert_eq!(4, mem::size_of::<BVec4>());
             assert_eq!(1, mem::align_of::<BVec4>());
         }