Description
Background
core::arch
contains architecture-specific vendor intrinsics, some of which have arguments that are required to be compile-time constants. This is tricky because LLVM will often assert/ICE if a constant is not passed into the LLVM intrinsic at the LLVM IR level. This was previously handled by using a match
statement to manually monomorphize the intrinsic:
#[inline]
#[target_feature(enable = "sse")]
#[cfg_attr(test, assert_instr(shufps, mask = 3))]
#[rustc_args_required_const(2)]
#[stable(feature = "simd_x86", since = "1.27.0")]
pub unsafe fn _mm_shuffle_ps(a: __m128, b: __m128, mask: i32) -> __m128 {
let mask = (mask & 0xFF) as u8;
macro_rules! shuffle_done {
($x01:expr, $x23:expr, $x45:expr, $x67:expr) => {
simd_shuffle4(a, b, [$x01, $x23, $x45, $x67])
};
}
macro_rules! shuffle_x67 {
($x01:expr, $x23:expr, $x45:expr) => {
match (mask >> 6) & 0b11 {
0b00 => shuffle_done!($x01, $x23, $x45, 4),
0b01 => shuffle_done!($x01, $x23, $x45, 5),
0b10 => shuffle_done!($x01, $x23, $x45, 6),
_ => shuffle_done!($x01, $x23, $x45, 7),
}
};
}
macro_rules! shuffle_x45 {
($x01:expr, $x23:expr) => {
match (mask >> 4) & 0b11 {
0b00 => shuffle_x67!($x01, $x23, 4),
0b01 => shuffle_x67!($x01, $x23, 5),
0b10 => shuffle_x67!($x01, $x23, 6),
_ => shuffle_x67!($x01, $x23, 7),
}
};
}
macro_rules! shuffle_x23 {
($x01:expr) => {
match (mask >> 2) & 0b11 {
0b00 => shuffle_x45!($x01, 0),
0b01 => shuffle_x45!($x01, 1),
0b10 => shuffle_x45!($x01, 2),
_ => shuffle_x45!($x01, 3),
}
};
}
match mask & 0b11 {
0b00 => shuffle_x23!(0),
0b01 => shuffle_x23!(1),
0b10 => shuffle_x23!(2),
_ => shuffle_x23!(3),
}
}
The downside of this approach is that it generates huge MIR and severely bloats the size of libcore
to the point where it actually affects compilation time.
The #[rustc_args_required_const]
attribute was used to restrict arguments to constants so that we could eventually replace this with const generics. Note that it effectively only acts as a hard lint, the match
is still needed to manually monomorphize the LLVM intrinsic.
New const generics support
All the intrinsics in stdarch
have been converted to use the newly stabilized const generics:
#[inline]
#[target_feature(enable = "sse")]
#[cfg_attr(test, assert_instr(shufps, mask = 3))]
#[rustc_legacy_const_generics(2)]
#[stable(feature = "simd_x86", since = "1.27.0")]
pub unsafe fn _mm_shuffle_ps<const mask: i32>(a: __m128, b: __m128) -> __m128 {
static_assert!(mask: i32 where mask >= 0 && mask <= 255);
simd_shuffle4(
a,
b,
[
mask as u32 & 0b11,
(mask as u32 >> 2) & 0b11,
((mask as u32 >> 4) & 0b11) + 4,
((mask as u32 >> 6) & 0b11) + 4,
],
)
}
To preserve backwards compatibility with the already stabilized intrinsics using #[rustc_args_required_const]
, a new attribute #[rustc_legacy_const_generics]
was added in #82447 which rewrites function calls of the form func(a, b, c)
to func::<{b}>(a, c)
.
This new attribute is not intended to ever be stabilized, it is only intended for use in stdarch
as a replacement for #[rustc_args_required_const]
.
#[rustc_legacy_const_generics(1)]
pub fn foo<const Y: usize>(x: usize, z: usize) -> [usize; 3] {
[x, Y, z]
}
fn main() {
assert_eq!(foo(0 + 0, 1 + 1, 2 + 2), [0, 2, 4]);
assert_eq!(foo::<{1 + 1}>(0 + 0, 2 + 2), [0, 2, 4]);
}
Open questions/issues
#[rustc_args_required_const]
allowed constant expressions derived from generic arguments in the caller, but this is not allowed with const generics. This technically makes this a breaking change.- Another potential breaking change is that the intrinsics now use a post-monomorphization
static_assert
to validate constant arguments. Previously, intrinsics would either use runtime asserts or simply pick an arbitrary default behavior for out-of-range constants. - Do we want to force all callers to use the const generics syntax in the 2021 edition?
- Pros:
- It makes it clearer that these intrinsics expect a constant as an argument.
- Intrinsics are rendered with the const generics syntax in rustdoc.
- Cons:
- We will still need to support the
#[rustc_legacy_const_generics]
argument rewriting anyways for 2015/2018 code. - Intrinsics no longer have the same function signature as the original C vendor intrinsics.
- We will still need to support the
- Pros: