Change:
<ins> // build-fail #![allow(non_camel_case_types)] #![feature(repr_simd, platform_intrinsics)] // Test for #73542 to verify out-of-bounds shuffle vectors do not compile. #[repr(simd)] #[derive(Copy, Clone)] struct u8x2(u8, u8); #[repr(simd)] #[derive(Copy, Clone)] struct u8x4(u8, u8, u8, u8); #[repr(simd)] #[derive(Copy, Clone)] struct u8x8(u8, u8, u8, u8, u8, u8, u8, u8); #[repr(simd)] #[derive(Copy, Clone)] struct u8x16( u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, ); #[repr(simd)] #[derive(Copy, Clone)] struct u8x32( u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, ); #[repr(simd)] #[derive(Copy, Clone)] struct u8x64( u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, ); // Test vectors by lane size. Since LLVM does not distinguish between a shuffle // over two f32s and a shuffle over two u64s, or any other such combination, // it is not necessary to test every possible vector, only lane counts. macro_rules! test_shuffle_lanes { ($n:literal, $x:ident, $y:ident, $t:tt) => { unsafe { let shuffle: $x = { const ARR: [u32; $n] = { let mut arr = [0; $n]; arr[0] = $n * 2; arr }; extern "platform-intrinsic" { pub fn $y<T, U>(x: T, y: T, idx: [u32; $n]) -> U; } let vec1 = $x$t; let vec2 = $x$t; $y(vec1, vec2, ARR) }; } } } //~^^^^^ ERROR: invalid monomorphization of `simd_shuffle2` intrinsic //~| ERROR: invalid monomorphization of `simd_shuffle4` intrinsic //~| ERROR: invalid monomorphization of `simd_shuffle8` intrinsic //~| ERROR: invalid monomorphization of `simd_shuffle16` intrinsic //~| ERROR: invalid monomorphization of `simd_shuffle32` intrinsic //~| ERROR: invalid monomorphization of `simd_shuffle64` intrinsic // Because the test is mostly embedded in a macro, all the errors have the same origin point. // And unfortunately, standard comments, as in the UI test harness, disappear in macros! fn main() { test_shuffle_lanes!(2, u8x2, simd_shuffle2, (2, 1)); test_shuffle_lanes!(4, u8x4, simd_shuffle4, (4, 3, 2, 1)); test_shuffle_lanes!(8, u8x8, simd_shuffle8, (8, 7, 6, 5, 4, 3, 2, 1)); test_shuffle_lanes!(16, u8x16, simd_shuffle16, (16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)); test_shuffle_lanes!(32, u8x32, simd_shuffle32, (32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)); test_shuffle_lanes!(64, u8x64, simd_shuffle64, (64, 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)); } </ins>

Discussion:
Looks like there are some questions regarding the semantics of shufflevector intrinsics in LLVM: So my question is, what are the semantics of those intrinsics in MIR? We had some and it seems like it should be one of the two: OOB shufflevector should be statically ruled out. We could even have the new MIR validation pass check that. OOB shufflevector returns "uninitialized memory" for the affected elements. (Following , MIR does not have a poison vs undef distinction; our "uninit" is closest to LLVM's poison.) The former seems definitely the safest ;) but currently this is . was concerned that stdarch might rely on OOB shufflevector somewhere. If we have consensus that for now, we should statically rule out OOB indices to avoid UB/uninit here, I guess the main open issue is (a) implementing that check and (b) fixing the fallout, if any. Cc (not sure who else to ping for stdarch)
Another option is that OOB values wrap to be inbounds. I think that we should use static denial of bad values, but wrapping is at least possible I suppose.
If the indices are always known statically then yes, we could statically wrap them.
I mentioned it because that's how intel shuffles are defined to work by intel. The intrinsic looks at particular bits within the control value and ignores the rest of the bits, so they end up being wrapping. I'm not yet familiar enough with ARM intrinsics to say how the hardware handles it.
We can restrict attention to constant shuffle indices. Architectures with "variable" shuffles exist but those capabilities are accessed by separate intrinsics (certainly today, and I think this makes sense in the long term too). However, I don't see any reason to build wrapping into the intrinsics. Hardware behavior is not a good reason IMO, since: general operation is different from and quite a lot more flexible than many real shuffle instructions, so there's no 1:1 correspondence anyway. rustc intrinsics in question are purely internal and perma-unstable, so if index wraparound is desirable in code targeting a particular platform, it can just as well be built into the core::arch functions and other wrappers, without affecting all other platforms.

In addition, although I just said we can focus on constant shuffle indices, I do think it would be nice to have some consistency between the intrinsics with constant indices and those with variable indices (current or future), which is easier to achieve with "hard error"/UB semantics than with implicit wrapping.
That is not immediately helpful though as LLVM makes this undef/poison. We'd still have to specify and perform the wrapping ourselves. Do we support something like that in Rust already, and are there any open questions around OOB semantics?
I am not sure. Certainly LLVM does not have a target-independent representation of such shuffles today, only target-specific intrinsics whose semantics may be unclear but probably follows the hardware in question (which generally have some defined behavior for those cases, e.g. ARM SVE and RISC-V V make those elements zero). If a target-independent representation is ever introduced, I expect the preferable behavior for LLVM will be "poison in the lanes where the index is OOB" and for Rust/MIR, anything that's at least as strict should be fine.
So I'm not familiar with LLVM's details here, is it the case that if a shuffle constant is "OOB" then all SIMD lanes become poison after the operation?
No, it is lane-wise undef (at least according to the quote in the first comment; I haven't verified this).
Well, I guess I'm a little lost on when you could have one lane be OOB but another not be OOB. For example, uses two bits per lane index and lets you pick from among 4 lanes when shuffling. It's fairly plain that a two bit value can't be OOB within a 4 lane selection. You could of course set bits above the 8th bit, but that doesn't affect the bits used to pick the source lanes for output lanes 0 through 3. So am I just looking at the wrong intrinsic when trying to come up with an example? Is there a different intrinsic I should be looking at? or put another way, could you show an example call to any intrinsic with a partial OOB control value?
This is about the (for N = 2, 4, 8, ...) intrinsics, which is used in the implementation of many core::arch shuffle/permute intrinsics (including ).

Like I said before, is a fair bit more general than many specific instructions (such as ), representing the shuffle indices as N constants. See for a small self-contained example.
Oops, the example I constructed is actually wrong because I forgot about the consecutive numbering of lanes of the two input vectors. Indices 4 and 5 (in that example) aren't OOB, they just refer to the other input vector. Indices that are actually OOB are already rejected:
Do we have tests that ensure this? I could not find any.