From 03306632ff7ac33d8d04f3a1cc6ab948af279496 Mon Sep 17 00:00:00 2001
From: Piotr Spieker
Date: Thu, 12 Feb 2026 13:18:59 +0100
Subject: [PATCH 1/4] Use the term struct-like variant instead of anonymous
structs for struct-like enum variants
This closer follows the terminology used in the Rust Book and Reference.
[Rust Book](https://doc.rust-lang.org/stable/book/ch06-01-defining-an-enum.html#listing-6-2):
> named fields, like a struct
[Reference](https://doc.rust-lang.org/stable/reference/items/enumerations.html#r-items.enum.constructor):
> struct-like enum variant
---
library/std/src/keyword_docs.rs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/library/std/src/keyword_docs.rs b/library/std/src/keyword_docs.rs
index 26f80fd90159..1c8927435c3a 100644
--- a/library/std/src/keyword_docs.rs
+++ b/library/std/src/keyword_docs.rs
@@ -418,7 +418,7 @@ mod else_keyword {}
/// The first enum shown is the usual kind of enum you'd find in a C-style language. The second
/// shows off a hypothetical example of something storing location data, with `Coord` being any
/// other type that's needed, for example a struct. The third example demonstrates the kind of
-/// data a variant can store, ranging from nothing, to a tuple, to an anonymous struct.
+/// data a variant can store, ranging from nothing, to a tuple, to a struct-like variant.
///
/// Instantiating enum variants involves explicitly using the enum's name as its namespace,
/// followed by one of its variants. `SimpleEnum::SecondVariant` would be an example from above.
From af80b0f2cd0505bcc86eaa675d1ab403110d373a Mon Sep 17 00:00:00 2001
From: Takayuki Maeda
Date: Mon, 13 Apr 2026 17:21:17 +0900
Subject: [PATCH 2/4] do not lint doc_cfg as an unused feature
---
compiler/rustc_middle/src/ty/context.rs | 6 +++++-
tests/ui/lint/unused-features/used-doc-cfg.rs | 8 ++++++++
2 files changed, 13 insertions(+), 1 deletion(-)
create mode 100644 tests/ui/lint/unused-features/used-doc-cfg.rs
diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs
index f0707cfbbb40..b908a6c6e843 100644
--- a/compiler/rustc_middle/src/ty/context.rs
+++ b/compiler/rustc_middle/src/ty/context.rs
@@ -44,7 +44,7 @@
use rustc_session::cstore::{CrateStoreDyn, Untracked};
use rustc_session::lint::Lint;
use rustc_span::def_id::{CRATE_DEF_ID, DefPathHash, StableCrateId};
-use rustc_span::{DUMMY_SP, Ident, Span, Symbol, kw};
+use rustc_span::{DUMMY_SP, Ident, Span, Symbol, kw, sym};
use rustc_type_ir::TyKind::*;
pub use rustc_type_ir::lift::Lift;
use rustc_type_ir::{CollectAndApply, TypeFlags, WithCachedTypeInfo, elaborate, search_graph};
@@ -1705,6 +1705,10 @@ struct UnusedFeature {
// in downstream crates. It should never be linted, but should we
// hack this in the linter to ignore it?
&& f.as_str() != "restricted_std"
+ // `doc_cfg` affects rustdoc behavior: rustdoc checks it via
+ // `tcx.features().doc_cfg()`, but a normal rustc compilation may
+ // never observe that use. Do not lint it as unused here.
+ && *f != sym::doc_cfg
})
.collect::>();
diff --git a/tests/ui/lint/unused-features/used-doc-cfg.rs b/tests/ui/lint/unused-features/used-doc-cfg.rs
new file mode 100644
index 000000000000..91c4a1600fc2
--- /dev/null
+++ b/tests/ui/lint/unused-features/used-doc-cfg.rs
@@ -0,0 +1,8 @@
+//@ check-pass
+//@ compile-flags: --check-cfg=cfg(feature,values("enabled_feature"))
+#![crate_type = "lib"]
+#![deny(unused_features)]
+#![feature(doc_cfg)]
+
+#[cfg(feature = "enabled_feature")]
+pub fn foo() {}
From 6f428df8dfee92b7b5125eb5c8f9d1e7b06e0f3f Mon Sep 17 00:00:00 2001
From: Folkert de Vries
Date: Wed, 8 Apr 2026 21:03:50 +0200
Subject: [PATCH 3/4] preseve SIMD element type information
and provide it to LLVM for better optimization
---
compiler/rustc_abi/src/callconv.rs | 5 +-
compiler/rustc_abi/src/callconv/reg.rs | 17 +++-
.../src/abi/pass_mode.rs | 4 +-
compiler/rustc_codegen_gcc/src/abi.rs | 4 +-
compiler/rustc_codegen_llvm/src/abi.rs | 30 +++++-
.../rustc_codegen_ssa/src/mir/naked_asm.rs | 2 +-
.../src/mono_checks/abi_check.rs | 7 +-
compiler/rustc_target/src/callconv/aarch64.rs | 2 +-
compiler/rustc_target/src/callconv/arm.rs | 2 +-
.../rustc_target/src/callconv/powerpc64.rs | 2 +-
compiler/rustc_target/src/callconv/s390x.rs | 4 +-
compiler/rustc_target/src/callconv/x86.rs | 9 +-
compiler/rustc_target/src/callconv/x86_64.rs | 2 +-
.../rustc_target/src/callconv/x86_win64.rs | 5 +-
tests/assembly-llvm/aarch64-vld2-s16.rs | 46 +++++++++
.../preserve-vec-element-types.rs | 98 +++++++++++++++++++
16 files changed, 212 insertions(+), 27 deletions(-)
create mode 100644 tests/assembly-llvm/aarch64-vld2-s16.rs
create mode 100644 tests/codegen-llvm/preserve-vec-element-types.rs
diff --git a/compiler/rustc_abi/src/callconv.rs b/compiler/rustc_abi/src/callconv.rs
index 7ad7088b3089..d6594e277f00 100644
--- a/compiler/rustc_abi/src/callconv.rs
+++ b/compiler/rustc_abi/src/callconv.rs
@@ -75,10 +75,11 @@ pub fn homogeneous_aggregate(&self, cx: &C) -> Result {
+ BackendRepr::SimdVector { element, count: _ } => {
assert!(!self.is_zst());
+
Ok(HomogeneousAggregate::Homogeneous(Reg {
- kind: RegKind::Vector,
+ kind: RegKind::Vector { hint_vector_elem: element.primitive() },
size: self.size,
}))
}
diff --git a/compiler/rustc_abi/src/callconv/reg.rs b/compiler/rustc_abi/src/callconv/reg.rs
index 66d4dca00726..c30425fb703a 100644
--- a/compiler/rustc_abi/src/callconv/reg.rs
+++ b/compiler/rustc_abi/src/callconv/reg.rs
@@ -1,14 +1,19 @@
#[cfg(feature = "nightly")]
use rustc_macros::HashStable_Generic;
-use crate::{Align, HasDataLayout, Size};
+use crate::{Align, HasDataLayout, Integer, Primitive, Size};
#[cfg_attr(feature = "nightly", derive(HashStable_Generic))]
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub enum RegKind {
Integer,
Float,
- Vector,
+ Vector {
+ /// The `hint_vector_elem` is strictly for optimization purposes. E.g. it can be used by
+ /// a codegen backend to prevent extra bitcasts that obscure a pattern. Alternatively,
+ /// it can be safely ignored by always picking i8.
+ hint_vector_elem: Primitive,
+ },
}
#[cfg_attr(feature = "nightly", derive(HashStable_Generic))]
@@ -36,6 +41,12 @@ impl Reg {
reg_ctor!(f32, Float, 32);
reg_ctor!(f64, Float, 64);
reg_ctor!(f128, Float, 128);
+
+ /// A vector of the given size with an unknown (and irrelevant) element type.
+ pub fn opaque_vector(size: Size) -> Reg {
+ // Default to an i8 vector of the given size.
+ Reg { kind: RegKind::Vector { hint_vector_elem: Primitive::Int(Integer::I8, true) }, size }
+ }
}
impl Reg {
@@ -58,7 +69,7 @@ pub fn align(&self, cx: &C) -> Align {
128 => dl.f128_align,
_ => panic!("unsupported float: {self:?}"),
},
- RegKind::Vector => dl.llvmlike_vector_align(self.size),
+ RegKind::Vector { .. } => dl.llvmlike_vector_align(self.size),
}
}
}
diff --git a/compiler/rustc_codegen_cranelift/src/abi/pass_mode.rs b/compiler/rustc_codegen_cranelift/src/abi/pass_mode.rs
index 44b63aa95f83..0283263cc604 100644
--- a/compiler/rustc_codegen_cranelift/src/abi/pass_mode.rs
+++ b/compiler/rustc_codegen_cranelift/src/abi/pass_mode.rs
@@ -26,7 +26,9 @@ fn reg_to_abi_param(reg: Reg) -> AbiParam {
(RegKind::Float, 4) => types::F32,
(RegKind::Float, 8) => types::F64,
(RegKind::Float, 16) => types::F128,
- (RegKind::Vector, size) => types::I8.by(u32::try_from(size).unwrap()).unwrap(),
+ (RegKind::Vector { hint_vector_elem: _ }, size) => {
+ types::I8.by(u32::try_from(size).unwrap()).unwrap()
+ }
_ => unreachable!("{:?}", reg),
};
AbiParam::new(clif_ty)
diff --git a/compiler/rustc_codegen_gcc/src/abi.rs b/compiler/rustc_codegen_gcc/src/abi.rs
index 4d1274a63d1f..8277231f16a5 100644
--- a/compiler/rustc_codegen_gcc/src/abi.rs
+++ b/compiler/rustc_codegen_gcc/src/abi.rs
@@ -90,7 +90,9 @@ fn gcc_type<'gcc>(&self, cx: &CodegenCx<'gcc, '_>) -> Type<'gcc> {
64 => cx.type_f64(),
_ => bug!("unsupported float: {:?}", self),
},
- RegKind::Vector => cx.type_vector(cx.type_i8(), self.size.bytes()),
+ RegKind::Vector { hint_vector_elem: _ } => {
+ cx.type_vector(cx.type_i8(), self.size.bytes())
+ }
}
}
}
diff --git a/compiler/rustc_codegen_llvm/src/abi.rs b/compiler/rustc_codegen_llvm/src/abi.rs
index a6e841e440a2..d474ba2d4ec7 100644
--- a/compiler/rustc_codegen_llvm/src/abi.rs
+++ b/compiler/rustc_codegen_llvm/src/abi.rs
@@ -2,8 +2,8 @@
use libc::c_uint;
use rustc_abi::{
- ArmCall, BackendRepr, CanonAbi, HasDataLayout, InterruptKind, Primitive, Reg, RegKind, Size,
- X86Call,
+ ArmCall, BackendRepr, CanonAbi, Float, HasDataLayout, Integer, InterruptKind, Primitive, Reg,
+ RegKind, Size, X86Call,
};
use rustc_codegen_ssa::MemFlags;
use rustc_codegen_ssa::mir::operand::{OperandRef, OperandValue};
@@ -137,7 +137,31 @@ fn llvm_type<'ll>(&self, cx: &CodegenCx<'ll, '_>) -> &'ll Type {
128 => cx.type_f128(),
_ => bug!("unsupported float: {:?}", self),
},
- RegKind::Vector => cx.type_vector(cx.type_i8(), self.size.bytes()),
+ RegKind::Vector { hint_vector_elem } => {
+ // NOTE: it is valid to ignore the element type hint (and always pick i8).
+ // But providing a more accurate type means fewer casts in LLVM IR,
+ // which helps with optimization.
+ let ty = match hint_vector_elem {
+ Primitive::Int(integer, _) => match integer {
+ Integer::I8 => cx.type_ix(8),
+ Integer::I16 => cx.type_ix(16),
+ Integer::I32 => cx.type_ix(32),
+ Integer::I64 => cx.type_ix(64),
+ Integer::I128 => cx.type_ix(128),
+ },
+ Primitive::Float(float) => match float {
+ Float::F16 => cx.type_f16(),
+ Float::F32 => cx.type_f32(),
+ Float::F64 => cx.type_f64(),
+ Float::F128 => cx.type_f128(),
+ },
+ Primitive::Pointer(_) => cx.type_ptr(),
+ };
+
+ assert!(self.size.bytes().is_multiple_of(hint_vector_elem.size(cx).bytes()));
+ let len = self.size.bytes() / hint_vector_elem.size(cx).bytes();
+ cx.type_vector(ty, len)
+ }
}
}
}
diff --git a/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs b/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs
index 7af10dbd042c..97cfc648b7cb 100644
--- a/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs
+++ b/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs
@@ -421,7 +421,7 @@ fn wasm_type<'tcx>(signature: &mut String, arg_abi: &ArgAbi<'_, Ty<'tcx>>, ptr_t
..=8 => "f64",
_ => ptr_type,
},
- RegKind::Vector => "v128",
+ RegKind::Vector { .. } => "v128",
};
signature.push_str(wrapped_wasm_type);
diff --git a/compiler/rustc_monomorphize/src/mono_checks/abi_check.rs b/compiler/rustc_monomorphize/src/mono_checks/abi_check.rs
index 0921e57844b0..41904d7905ef 100644
--- a/compiler/rustc_monomorphize/src/mono_checks/abi_check.rs
+++ b/compiler/rustc_monomorphize/src/mono_checks/abi_check.rs
@@ -25,8 +25,11 @@ fn passes_vectors_by_value(mode: &PassMode, repr: &BackendRepr) -> UsesVectorReg
match mode {
PassMode::Ignore | PassMode::Indirect { .. } => UsesVectorRegisters::No,
PassMode::Cast { pad_i32: _, cast }
- if cast.prefix.iter().any(|r| r.is_some_and(|x| x.kind == RegKind::Vector))
- || cast.rest.unit.kind == RegKind::Vector =>
+ if cast
+ .prefix
+ .iter()
+ .any(|r| r.is_some_and(|x| matches!(x.kind, RegKind::Vector { .. })))
+ || matches!(cast.rest.unit.kind, RegKind::Vector { .. }) =>
{
UsesVectorRegisters::FixedVector
}
diff --git a/compiler/rustc_target/src/callconv/aarch64.rs b/compiler/rustc_target/src/callconv/aarch64.rs
index e9a19aa7024b..ce69427cbdd5 100644
--- a/compiler/rustc_target/src/callconv/aarch64.rs
+++ b/compiler/rustc_target/src/callconv/aarch64.rs
@@ -35,7 +35,7 @@ fn is_homogeneous_aggregate<'a, Ty, C>(cx: &C, arg: &mut ArgAbi<'a, Ty>) -> Opti
// The softfloat ABI treats floats like integers, so they
// do not get homogeneous aggregate treatment.
RegKind::Float => cx.target_spec().rustc_abi != Some(RustcAbi::Softfloat),
- RegKind::Vector => size.bits() == 64 || size.bits() == 128,
+ RegKind::Vector { .. } => size.bits() == 64 || size.bits() == 128,
};
valid_unit.then_some(Uniform::consecutive(unit, size))
diff --git a/compiler/rustc_target/src/callconv/arm.rs b/compiler/rustc_target/src/callconv/arm.rs
index 4c1ff27aac50..41c3a0a0210f 100644
--- a/compiler/rustc_target/src/callconv/arm.rs
+++ b/compiler/rustc_target/src/callconv/arm.rs
@@ -19,7 +19,7 @@ fn is_homogeneous_aggregate<'a, Ty, C>(cx: &C, arg: &mut ArgAbi<'a, Ty>) -> Opti
let valid_unit = match unit.kind {
RegKind::Integer => false,
RegKind::Float => true,
- RegKind::Vector => size.bits() == 64 || size.bits() == 128,
+ RegKind::Vector { .. } => size.bits() == 64 || size.bits() == 128,
};
valid_unit.then_some(Uniform::consecutive(unit, size))
diff --git a/compiler/rustc_target/src/callconv/powerpc64.rs b/compiler/rustc_target/src/callconv/powerpc64.rs
index d807617491d1..6a8a6841781c 100644
--- a/compiler/rustc_target/src/callconv/powerpc64.rs
+++ b/compiler/rustc_target/src/callconv/powerpc64.rs
@@ -36,7 +36,7 @@ fn is_homogeneous_aggregate<'a, Ty, C>(
let valid_unit = match unit.kind {
RegKind::Integer => false,
RegKind::Float => true,
- RegKind::Vector => arg.layout.size.bits() == 128,
+ RegKind::Vector { .. } => arg.layout.size.bits() == 128,
};
valid_unit.then_some(Uniform::consecutive(unit, arg.layout.size))
diff --git a/compiler/rustc_target/src/callconv/s390x.rs b/compiler/rustc_target/src/callconv/s390x.rs
index a2ff6f5a3a03..581c1e2e862c 100644
--- a/compiler/rustc_target/src/callconv/s390x.rs
+++ b/compiler/rustc_target/src/callconv/s390x.rs
@@ -3,7 +3,7 @@
use rustc_abi::{BackendRepr, HasDataLayout, TyAbiInterface};
-use crate::callconv::{ArgAbi, FnAbi, Reg, RegKind};
+use crate::callconv::{ArgAbi, FnAbi, Reg};
use crate::spec::{Env, HasTargetSpec, Os};
fn classify_ret(ret: &mut ArgAbi<'_, Ty>) {
@@ -51,7 +51,7 @@ fn classify_arg<'a, Ty, C>(cx: &C, arg: &mut ArgAbi<'a, Ty>)
if arg.layout.is_single_vector_element(cx, size) {
// pass non-transparent wrappers around a vector as `PassMode::Cast`
- arg.cast_to(Reg { kind: RegKind::Vector, size });
+ arg.cast_to(Reg::opaque_vector(size));
return;
}
}
diff --git a/compiler/rustc_target/src/callconv/x86.rs b/compiler/rustc_target/src/callconv/x86.rs
index 9aaa411db6c0..81ff1a2a4590 100644
--- a/compiler/rustc_target/src/callconv/x86.rs
+++ b/compiler/rustc_target/src/callconv/x86.rs
@@ -1,9 +1,8 @@
use rustc_abi::{
- AddressSpace, Align, BackendRepr, HasDataLayout, Primitive, Reg, RegKind, TyAbiInterface,
- TyAndLayout,
+ AddressSpace, Align, BackendRepr, HasDataLayout, Primitive, Reg, RegKind, TyAndLayout,
};
-use crate::callconv::{ArgAttribute, FnAbi, PassMode};
+use crate::callconv::{ArgAttribute, FnAbi, PassMode, TyAbiInterface};
use crate::spec::{HasTargetSpec, RustcAbi};
#[derive(PartialEq)]
@@ -175,7 +174,7 @@ pub(crate) fn fill_inregs<'a, Ty, C>(
// At this point we know this must be a primitive of sorts.
let unit = arg.layout.homogeneous_aggregate(cx).unwrap().unit().unwrap();
assert_eq!(unit.size, arg.layout.size);
- if matches!(unit.kind, RegKind::Float | RegKind::Vector) {
+ if matches!(unit.kind, RegKind::Float | RegKind::Vector { .. }) {
continue;
}
@@ -226,7 +225,7 @@ pub(crate) fn compute_rust_abi_info<'a, Ty, C>(cx: &C, fn_abi: &mut FnAbi<'a, Ty
// This is a single scalar that fits into an SSE register, and the target uses the
// SSE ABI. We prefer this over integer registers as float scalars need to be in SSE
// registers for float operations, so that's the best place to pass them around.
- fn_abi.ret.cast_to(Reg { kind: RegKind::Vector, size: fn_abi.ret.layout.size });
+ fn_abi.ret.cast_to(Reg::opaque_vector(fn_abi.ret.layout.size));
} else if fn_abi.ret.layout.size <= Primitive::Pointer(AddressSpace::ZERO).size(cx) {
// Same size or smaller than pointer, return in an integer register.
fn_abi.ret.cast_to(Reg { kind: RegKind::Integer, size: fn_abi.ret.layout.size });
diff --git a/compiler/rustc_target/src/callconv/x86_64.rs b/compiler/rustc_target/src/callconv/x86_64.rs
index dc73907c0c18..3055d18ffa01 100644
--- a/compiler/rustc_target/src/callconv/x86_64.rs
+++ b/compiler/rustc_target/src/callconv/x86_64.rs
@@ -151,7 +151,7 @@ fn reg_component(cls: &[Option], i: &mut usize, size: Size) -> Option Reg::f64(),
}
} else {
- Reg { kind: RegKind::Vector, size: Size::from_bytes(8) * (vec_len as u64) }
+ Reg::opaque_vector(Size::from_bytes(8) * (vec_len as u64))
})
}
Some(c) => unreachable!("reg_component: unhandled class {:?}", c),
diff --git a/compiler/rustc_target/src/callconv/x86_win64.rs b/compiler/rustc_target/src/callconv/x86_win64.rs
index 624563c68e5b..cece9d032b53 100644
--- a/compiler/rustc_target/src/callconv/x86_win64.rs
+++ b/compiler/rustc_target/src/callconv/x86_win64.rs
@@ -1,4 +1,4 @@
-use rustc_abi::{BackendRepr, Float, Integer, Primitive, RegKind, Size, TyAbiInterface};
+use rustc_abi::{BackendRepr, Float, Integer, Primitive, Size, TyAbiInterface};
use crate::callconv::{ArgAbi, FnAbi, Reg};
use crate::spec::{HasTargetSpec, RustcAbi};
@@ -33,8 +33,7 @@ pub(crate) fn compute_abi_info<'a, Ty, C: HasTargetSpec>(cx: &C, fn_abi: &mut Fn
} else {
// `i128` is returned in xmm0 by Clang and GCC
// FIXME(#134288): This may change for the `-msvc` targets in the future.
- let reg = Reg { kind: RegKind::Vector, size: Size::from_bits(128) };
- a.cast_to(reg);
+ a.cast_to(Reg::opaque_vector(Size::from_bits(128)));
}
} else if a.layout.size.bytes() > 8
&& !matches!(scalar.primitive(), Primitive::Float(Float::F128))
diff --git a/tests/assembly-llvm/aarch64-vld2-s16.rs b/tests/assembly-llvm/aarch64-vld2-s16.rs
new file mode 100644
index 000000000000..137422f30019
--- /dev/null
+++ b/tests/assembly-llvm/aarch64-vld2-s16.rs
@@ -0,0 +1,46 @@
+//@ assembly-output: emit-asm
+//@ compile-flags: -Copt-level=3
+//@ only-aarch64-unknown-linux-gnu
+#![feature(repr_simd, portable_simd, core_intrinsics, f16, f128)]
+#![crate_type = "lib"]
+#![allow(non_camel_case_types)]
+
+// Test `vld_s16` can be implemented in a portable way (i.e. without using LLVM neon intrinsics).
+// This relies on rust preserving the SIMD vector element type and using it to construct the
+// LLVM type. Without this information, additional casts are needed that defeat the LLVM pattern
+// matcher, see https://github.com/llvm/llvm-project/issues/181514.
+
+use std::mem::transmute;
+use std::simd::Simd;
+
+#[unsafe(no_mangle)]
+#[target_feature(enable = "neon")]
+unsafe extern "C" fn vld2_s16_old(ptr: *const i16) -> std::arch::aarch64::int16x4x2_t {
+ // CHECK-LABEL: vld2_s16_old
+ // CHECK: .cfi_startproc
+ // CHECK-NEXT: ld2 { v0.4h, v1.4h }, [x0]
+ // CHECK-NEXT: ret
+ std::arch::aarch64::vld2_s16(ptr)
+}
+
+#[unsafe(no_mangle)]
+#[target_feature(enable = "neon")]
+unsafe extern "C" fn vld2_s16_new(a: *const i16) -> std::arch::aarch64::int16x4x2_t {
+ // CHECK-LABEL: vld2_s16_new
+ // CHECK: .cfi_startproc
+ // CHECK-NEXT: ld2 { v0.4h, v1.4h }, [x0]
+ // CHECK-NEXT: ret
+
+ type V = Simd;
+ type W = Simd;
+
+ let w: W = std::ptr::read_unaligned(a as *const W);
+
+ #[repr(simd)]
+ pub(crate) struct SimdShuffleIdx([u32; LEN]);
+
+ let v0: V = std::intrinsics::simd::simd_shuffle(w, w, const { SimdShuffleIdx([0, 2, 4, 6]) });
+ let v1: V = std::intrinsics::simd::simd_shuffle(w, w, const { SimdShuffleIdx([1, 3, 5, 7]) });
+
+ transmute((v0, v1))
+}
diff --git a/tests/codegen-llvm/preserve-vec-element-types.rs b/tests/codegen-llvm/preserve-vec-element-types.rs
new file mode 100644
index 000000000000..bfa45177a952
--- /dev/null
+++ b/tests/codegen-llvm/preserve-vec-element-types.rs
@@ -0,0 +1,98 @@
+// ignore-tidy-linelength
+//@ compile-flags: -Copt-level=3 -Zmerge-functions=disabled --target=aarch64-unknown-linux-gnu
+//@ needs-llvm-components: aarch64
+//@ add-minicore
+#![feature(no_core, repr_simd, f16, f128)]
+#![crate_type = "lib"]
+#![no_std]
+#![no_core]
+#![allow(non_camel_case_types)]
+
+// Test that the SIMD vector element type is preserved. This is not required for correctness, but
+// useful for optimization. It prevents additional bitcasts that make LLVM patterns fail.
+
+extern crate minicore;
+use minicore::*;
+
+#[repr(simd)]
+pub struct Simd([T; N]);
+
+#[repr(C)]
+struct Pair(T, T);
+
+#[repr(C)]
+struct Triple(T, T, T);
+
+#[repr(C)]
+struct Quad(T, T, T, T);
+
+#[rustfmt::skip]
+mod tests {
+ use super::*;
+
+ // CHECK: define [2 x <8 x i8>] @pair_int8x8_t([2 x <8 x i8>] {{.*}} %0)
+ #[unsafe(no_mangle)] extern "C" fn pair_int8x8_t(x: Pair>) -> Pair> { x }
+
+ // CHECK: define [2 x <4 x i16>] @pair_int16x4_t([2 x <4 x i16>] {{.*}} %0)
+ #[unsafe(no_mangle)] extern "C" fn pair_int16x4_t(x: Pair>) -> Pair> { x }
+
+ // CHECK: define [2 x <2 x i32>] @pair_int32x2_t([2 x <2 x i32>] {{.*}} %0)
+ #[unsafe(no_mangle)] extern "C" fn pair_int32x2_t(x: Pair>) -> Pair> { x }
+
+ // CHECK: define [2 x <1 x i64>] @pair_int64x1_t([2 x <1 x i64>] {{.*}} %0)
+ #[unsafe(no_mangle)] extern "C" fn pair_int64x1_t(x: Pair>) -> Pair> { x }
+
+ // CHECK: define [2 x <4 x half>] @pair_float16x4_t([2 x <4 x half>] {{.*}} %0)
+ #[unsafe(no_mangle)] extern "C" fn pair_float16x4_t(x: Pair>) -> Pair> { x }
+
+ // CHECK: define [2 x <2 x float>] @pair_float32x2_t([2 x <2 x float>] {{.*}} %0)
+ #[unsafe(no_mangle)] extern "C" fn pair_float32x2_t(x: Pair>) -> Pair> { x }
+
+ // CHECK: define [2 x <1 x double>] @pair_float64x1_t([2 x <1 x double>] {{.*}} %0)
+ #[unsafe(no_mangle)] extern "C" fn pair_float64x1_t(x: Pair>) -> Pair> { x }
+
+ // CHECK: define [2 x <1 x ptr>] @pair_ptrx1_t([2 x <1 x ptr>] {{.*}} %0)
+ #[unsafe(no_mangle)] extern "C" fn pair_ptrx1_t(x: Pair>) -> Pair> { x }
+
+ // When it fits in a 128-bit register, it's passed directly.
+
+ // CHECK: define [4 x <4 x i8>] @quad_int8x4_t([4 x <4 x i8>] {{.*}} %0)
+ #[unsafe(no_mangle)] extern "C" fn quad_int8x4_t(x: Quad>) -> Quad> { x }
+
+ // CHECK: define [4 x <2 x i16>] @quad_int16x2_t([4 x <2 x i16>] {{.*}} %0)
+ #[unsafe(no_mangle)] extern "C" fn quad_int16x2_t(x: Quad>) -> Quad> { x }
+
+ // CHECK: define [4 x <1 x i32>] @quad_int32x1_t([4 x <1 x i32>] {{.*}} %0)
+ #[unsafe(no_mangle)] extern "C" fn quad_int32x1_t(x: Quad>) -> Quad> { x }
+
+ // CHECK: define [4 x <2 x half>] @quad_float16x2_t([4 x <2 x half>] {{.*}} %0)
+ #[unsafe(no_mangle)] extern "C" fn quad_float16x2_t(x: Quad>) -> Quad> { x }
+
+ // CHECK: define [4 x <1 x float>] @quad_float32x1_t([4 x <1 x float>] {{.*}} %0)
+ #[unsafe(no_mangle)] extern "C" fn quad_float32x1_t(x: Quad>) -> Quad> { x }
+
+ // When it doesn't quite fit, padding is added which does erase the type.
+
+ // CHECK: define [2 x i64] @triple_int8x4_t
+ #[unsafe(no_mangle)] extern "C" fn triple_int8x4_t(x: Triple>) -> Triple> { x }
+
+ // Other configurations are not passed by-value but indirectly.
+
+ // CHECK: define void @pair_int128x1_t
+ #[unsafe(no_mangle)] extern "C" fn pair_int128x1_t(x: Pair>) -> Pair> { x }
+
+ // CHECK: define void @pair_float128x1_t
+ #[unsafe(no_mangle)] extern "C" fn pair_float128x1_t(x: Pair>) -> Pair> { x }
+
+ // CHECK: define void @pair_int8x16_t
+ #[unsafe(no_mangle)] extern "C" fn pair_int8x16_t(x: Pair>) -> Pair> { x }
+
+ // CHECK: define void @pair_int16x8_t
+ #[unsafe(no_mangle)] extern "C" fn pair_int16x8_t(x: Pair>) -> Pair> { x }
+
+ // CHECK: define void @triple_int16x8_t
+ #[unsafe(no_mangle)] extern "C" fn triple_int16x8_t(x: Triple>) -> Triple> { x }
+
+ // CHECK: define void @quad_int16x8_t
+ #[unsafe(no_mangle)] extern "C" fn quad_int16x8_t(x: Quad>) -> Quad> { x }
+}
From 5c80d9031dcba3f8e49ce8fb131b6248f2249bca Mon Sep 17 00:00:00 2001
From: Takayuki Maeda
Date: Tue, 14 Apr 2026 02:08:02 +0900
Subject: [PATCH 4/4] add a link to issue 154487
---
tests/ui/lint/unused-features/used-doc-cfg.rs | 2 ++
1 file changed, 2 insertions(+)
diff --git a/tests/ui/lint/unused-features/used-doc-cfg.rs b/tests/ui/lint/unused-features/used-doc-cfg.rs
index 91c4a1600fc2..edd1dd6b10ce 100644
--- a/tests/ui/lint/unused-features/used-doc-cfg.rs
+++ b/tests/ui/lint/unused-features/used-doc-cfg.rs
@@ -1,5 +1,7 @@
//@ check-pass
//@ compile-flags: --check-cfg=cfg(feature,values("enabled_feature"))
+// Regression test for https://github.com/rust-lang/rust/issues/154487
+
#![crate_type = "lib"]
#![deny(unused_features)]
#![feature(doc_cfg)]