about summary refs log tree commit diff
diff options
context:
space:
mode:
authorJubilee <workingjubilee@gmail.com>2024-10-21 20:32:00 -0700
committerGitHub <noreply@github.com>2024-10-21 20:32:00 -0700
commitfe2cbbd2d571965d30c6f25c6d865babf4529e85 (patch)
tree926b21e859f7da2cb1236dabe70dea166c8f481c
parent814df6e50eaf89b90793e7d9618bb60f1f18377a (diff)
parent37dc4ec8d6519d7ba1ed8a11fb57ff4d1eb995dd (diff)
downloadrust-fe2cbbd2d571965d30c6f25c6d865babf4529e85.tar.gz
rust-fe2cbbd2d571965d30c6f25c6d865babf4529e85.zip
Rollup merge of #130432 - azhogin:azhogin/regparm, r=workingjubilee,pnkfelix
rust_for_linux: -Zregparm=<N> commandline flag for X86 (#116972)

Command line flag `-Zregparm=<N>` for X86 (32-bit) for rust-for-linux: https://github.com/rust-lang/rust/issues/116972
Implemented in the similar way as fastcall/vectorcall support (args are marked InReg if fit).
-rw-r--r--compiler/rustc_codegen_gcc/src/builder.rs8
-rw-r--r--compiler/rustc_codegen_gcc/src/context.rs10
-rw-r--r--compiler/rustc_interface/src/tests.rs1
-rw-r--r--compiler/rustc_middle/src/ty/layout.rs16
-rw-r--r--compiler/rustc_session/messages.ftl3
-rw-r--r--compiler/rustc_session/src/errors.rs10
-rw-r--r--compiler/rustc_session/src/options.rs5
-rw-r--r--compiler/rustc_session/src/session.rs9
-rw-r--r--compiler/rustc_target/src/callconv/mod.rs22
-rw-r--r--compiler/rustc_target/src/callconv/x86.rs104
-rw-r--r--compiler/rustc_target/src/spec/mod.rs12
-rw-r--r--src/doc/unstable-book/src/compiler-flags/regparm.md20
-rw-r--r--tests/codegen/regparm-inreg.rs125
-rw-r--r--tests/ui/invalid-compile-flags/regparm/regparm-valid-values.regparm4.stderr4
-rw-r--r--tests/ui/invalid-compile-flags/regparm/regparm-valid-values.rs24
-rw-r--r--tests/ui/invalid-compile-flags/regparm/requires-x86.aarch64.stderr4
-rw-r--r--tests/ui/invalid-compile-flags/regparm/requires-x86.rs21
-rw-r--r--tests/ui/invalid-compile-flags/regparm/requires-x86.x86_64.stderr4
18 files changed, 350 insertions, 52 deletions
diff --git a/compiler/rustc_codegen_gcc/src/builder.rs b/compiler/rustc_codegen_gcc/src/builder.rs
index b611f9ba8bc..45738068509 100644
--- a/compiler/rustc_codegen_gcc/src/builder.rs
+++ b/compiler/rustc_codegen_gcc/src/builder.rs
@@ -30,7 +30,7 @@ use rustc_middle::ty::{Instance, ParamEnv, Ty, TyCtxt};
 use rustc_span::Span;
 use rustc_span::def_id::DefId;
 use rustc_target::abi::call::FnAbi;
-use rustc_target::spec::{HasTargetSpec, HasWasmCAbiOpt, Target, WasmCAbi};
+use rustc_target::spec::{HasTargetSpec, HasWasmCAbiOpt, HasX86AbiOpt, Target, WasmCAbi, X86Abi};
 
 use crate::common::{SignType, TypeReflection, type_is_pointer};
 use crate::context::CodegenCx;
@@ -2347,6 +2347,12 @@ impl<'tcx> HasWasmCAbiOpt for Builder<'_, '_, 'tcx> {
     }
 }
 
+impl<'tcx> HasX86AbiOpt for Builder<'_, '_, 'tcx> {
+    fn x86_abi_opt(&self) -> X86Abi {
+        self.cx.x86_abi_opt()
+    }
+}
+
 pub trait ToGccComp {
     fn to_gcc_comparison(&self) -> ComparisonOp;
 }
diff --git a/compiler/rustc_codegen_gcc/src/context.rs b/compiler/rustc_codegen_gcc/src/context.rs
index 7cb49bf7991..707b35967a6 100644
--- a/compiler/rustc_codegen_gcc/src/context.rs
+++ b/compiler/rustc_codegen_gcc/src/context.rs
@@ -19,7 +19,9 @@ use rustc_session::Session;
 use rustc_span::source_map::respan;
 use rustc_span::{DUMMY_SP, Span};
 use rustc_target::abi::{HasDataLayout, PointeeInfo, Size, TargetDataLayout, VariantIdx};
-use rustc_target::spec::{HasTargetSpec, HasWasmCAbiOpt, Target, TlsModel, WasmCAbi};
+use rustc_target::spec::{
+    HasTargetSpec, HasWasmCAbiOpt, HasX86AbiOpt, Target, TlsModel, WasmCAbi, X86Abi,
+};
 
 use crate::callee::get_fn;
 use crate::common::SignType;
@@ -538,6 +540,12 @@ impl<'gcc, 'tcx> HasWasmCAbiOpt for CodegenCx<'gcc, 'tcx> {
     }
 }
 
+impl<'gcc, 'tcx> HasX86AbiOpt for CodegenCx<'gcc, 'tcx> {
+    fn x86_abi_opt(&self) -> X86Abi {
+        X86Abi { regparm: self.tcx.sess.opts.unstable_opts.regparm }
+    }
+}
+
 impl<'gcc, 'tcx> LayoutOfHelpers<'tcx> for CodegenCx<'gcc, 'tcx> {
     #[inline]
     fn handle_layout_err(&self, err: LayoutError<'tcx>, span: Span, ty: Ty<'tcx>) -> ! {
diff --git a/compiler/rustc_interface/src/tests.rs b/compiler/rustc_interface/src/tests.rs
index 3189620e969..d3762e739db 100644
--- a/compiler/rustc_interface/src/tests.rs
+++ b/compiler/rustc_interface/src/tests.rs
@@ -836,6 +836,7 @@ fn test_unstable_options_tracking_hash() {
     tracked!(profile_emit, Some(PathBuf::from("abc")));
     tracked!(profile_sample_use, Some(PathBuf::from("abc")));
     tracked!(profiler_runtime, "abc".to_string());
+    tracked!(regparm, Some(3));
     tracked!(relax_elf_relocations, Some(true));
     tracked!(remap_cwd_prefix, Some(PathBuf::from("abc")));
     tracked!(sanitizer, SanitizerSet::ADDRESS);
diff --git a/compiler/rustc_middle/src/ty/layout.rs b/compiler/rustc_middle/src/ty/layout.rs
index 3670b6bbc77..dbf88e643c5 100644
--- a/compiler/rustc_middle/src/ty/layout.rs
+++ b/compiler/rustc_middle/src/ty/layout.rs
@@ -21,7 +21,9 @@ use rustc_span::{DUMMY_SP, ErrorGuaranteed, Span};
 use rustc_target::abi::call::FnAbi;
 use rustc_target::abi::{FieldIdx, TyAbiInterface, VariantIdx, call};
 use rustc_target::spec::abi::Abi as SpecAbi;
-use rustc_target::spec::{HasTargetSpec, HasWasmCAbiOpt, PanicStrategy, Target, WasmCAbi};
+use rustc_target::spec::{
+    HasTargetSpec, HasWasmCAbiOpt, HasX86AbiOpt, PanicStrategy, Target, WasmCAbi, X86Abi,
+};
 use tracing::debug;
 use {rustc_abi as abi, rustc_hir as hir};
 
@@ -544,6 +546,12 @@ impl<'tcx> HasWasmCAbiOpt for TyCtxt<'tcx> {
     }
 }
 
+impl<'tcx> HasX86AbiOpt for TyCtxt<'tcx> {
+    fn x86_abi_opt(&self) -> X86Abi {
+        X86Abi { regparm: self.sess.opts.unstable_opts.regparm }
+    }
+}
+
 impl<'tcx> HasTyCtxt<'tcx> for TyCtxt<'tcx> {
     #[inline]
     fn tcx(&self) -> TyCtxt<'tcx> {
@@ -595,6 +603,12 @@ impl<'tcx> HasWasmCAbiOpt for LayoutCx<'tcx> {
     }
 }
 
+impl<'tcx> HasX86AbiOpt for LayoutCx<'tcx> {
+    fn x86_abi_opt(&self) -> X86Abi {
+        self.calc.cx.x86_abi_opt()
+    }
+}
+
 impl<'tcx> HasTyCtxt<'tcx> for LayoutCx<'tcx> {
     fn tcx(&self) -> TyCtxt<'tcx> {
         self.calc.cx
diff --git a/compiler/rustc_session/messages.ftl b/compiler/rustc_session/messages.ftl
index 1816d1278fe..893c532f1fb 100644
--- a/compiler/rustc_session/messages.ftl
+++ b/compiler/rustc_session/messages.ftl
@@ -136,3 +136,6 @@ session_unsupported_crate_type_for_target =
     dropping unsupported crate type `{$crate_type}` for target `{$target_triple}`
 
 session_unsupported_dwarf_version = requested DWARF version {$dwarf_version} is greater than 5
+
+session_unsupported_regparm = `-Zregparm={$regparm}` is unsupported (valid values 0-3)
+session_unsupported_regparm_arch = `-Zregparm=N` is only supported on x86
diff --git a/compiler/rustc_session/src/errors.rs b/compiler/rustc_session/src/errors.rs
index dbb74d1e244..20e8fb38b88 100644
--- a/compiler/rustc_session/src/errors.rs
+++ b/compiler/rustc_session/src/errors.rs
@@ -486,6 +486,16 @@ pub(crate) struct FunctionReturnRequiresX86OrX8664;
 pub(crate) struct FunctionReturnThunkExternRequiresNonLargeCodeModel;
 
 #[derive(Diagnostic)]
+#[diag(session_unsupported_regparm)]
+pub(crate) struct UnsupportedRegparm {
+    pub(crate) regparm: u32,
+}
+
+#[derive(Diagnostic)]
+#[diag(session_unsupported_regparm_arch)]
+pub(crate) struct UnsupportedRegparmArch;
+
+#[derive(Diagnostic)]
 #[diag(session_failed_to_create_profiler)]
 pub(crate) struct FailedToCreateProfiler {
     pub(crate) err: String,
diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs
index f9964b59a94..54a4621db24 100644
--- a/compiler/rustc_session/src/options.rs
+++ b/compiler/rustc_session/src/options.rs
@@ -2000,6 +2000,11 @@ options! {
         "enable queries of the dependency graph for regression testing (default: no)"),
     randomize_layout: bool = (false, parse_bool, [TRACKED],
         "randomize the layout of types (default: no)"),
+    regparm: Option<u32> = (None, parse_opt_number, [TRACKED],
+        "On x86-32 targets, setting this to N causes the compiler to pass N arguments \
+        in registers EAX, EDX, and ECX instead of on the stack for\
+        \"C\", \"cdecl\", and \"stdcall\" fn.\
+        It is UNSOUND to link together crates that use different values for this flag!"),
     relax_elf_relocations: Option<bool> = (None, parse_opt_bool, [TRACKED],
         "whether ELF relocations can be relaxed"),
     remap_cwd_prefix: Option<PathBuf> = (None, parse_opt_pathbuf, [TRACKED],
diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs
index 27879d817b2..1963cf4eb7c 100644
--- a/compiler/rustc_session/src/session.rs
+++ b/compiler/rustc_session/src/session.rs
@@ -1337,6 +1337,15 @@ fn validate_commandline_args_with_session_available(sess: &Session) {
         }
     }
 
+    if let Some(regparm) = sess.opts.unstable_opts.regparm {
+        if regparm > 3 {
+            sess.dcx().emit_err(errors::UnsupportedRegparm { regparm });
+        }
+        if sess.target.arch != "x86" {
+            sess.dcx().emit_err(errors::UnsupportedRegparmArch);
+        }
+    }
+
     // The code model check applies to `thunk` and `thunk-extern`, but not `thunk-inline`, so it is
     // kept as a `match` to force a change if new ones are added, even if we currently only support
     // `thunk-extern` like Clang.
diff --git a/compiler/rustc_target/src/callconv/mod.rs b/compiler/rustc_target/src/callconv/mod.rs
index 832246495bc..5d120a68059 100644
--- a/compiler/rustc_target/src/callconv/mod.rs
+++ b/compiler/rustc_target/src/callconv/mod.rs
@@ -6,7 +6,7 @@ use rustc_macros::HashStable_Generic;
 use rustc_span::Symbol;
 
 use crate::abi::{self, Abi, Align, HasDataLayout, Size, TyAbiInterface, TyAndLayout};
-use crate::spec::{self, HasTargetSpec, HasWasmCAbiOpt, WasmCAbi};
+use crate::spec::{self, HasTargetSpec, HasWasmCAbiOpt, HasX86AbiOpt, WasmCAbi};
 
 mod aarch64;
 mod amdgpu;
@@ -631,7 +631,7 @@ impl<'a, Ty> FnAbi<'a, Ty> {
     ) -> Result<(), AdjustForForeignAbiError>
     where
         Ty: TyAbiInterface<'a, C> + Copy,
-        C: HasDataLayout + HasTargetSpec + HasWasmCAbiOpt,
+        C: HasDataLayout + HasTargetSpec + HasWasmCAbiOpt + HasX86AbiOpt,
     {
         if abi == spec::abi::Abi::X86Interrupt {
             if let Some(arg) = self.args.first_mut() {
@@ -643,14 +643,18 @@ impl<'a, Ty> FnAbi<'a, Ty> {
         let spec = cx.target_spec();
         match &spec.arch[..] {
             "x86" => {
-                let flavor = if let spec::abi::Abi::Fastcall { .. }
-                | spec::abi::Abi::Vectorcall { .. } = abi
-                {
-                    x86::Flavor::FastcallOrVectorcall
-                } else {
-                    x86::Flavor::General
+                let (flavor, regparm) = match abi {
+                    spec::abi::Abi::Fastcall { .. } | spec::abi::Abi::Vectorcall { .. } => {
+                        (x86::Flavor::FastcallOrVectorcall, None)
+                    }
+                    spec::abi::Abi::C { .. }
+                    | spec::abi::Abi::Cdecl { .. }
+                    | spec::abi::Abi::Stdcall { .. } => {
+                        (x86::Flavor::General, cx.x86_abi_opt().regparm)
+                    }
+                    _ => (x86::Flavor::General, None),
                 };
-                x86::compute_abi_info(cx, self, flavor);
+                x86::compute_abi_info(cx, self, x86::X86Options { flavor, regparm });
             }
             "x86_64" => match abi {
                 spec::abi::Abi::SysV64 { .. } => x86_64::compute_abi_info(cx, self),
diff --git a/compiler/rustc_target/src/callconv/x86.rs b/compiler/rustc_target/src/callconv/x86.rs
index d9af83d3205..40c3e7a891a 100644
--- a/compiler/rustc_target/src/callconv/x86.rs
+++ b/compiler/rustc_target/src/callconv/x86.rs
@@ -8,7 +8,12 @@ pub(crate) enum Flavor {
     FastcallOrVectorcall,
 }
 
-pub(crate) fn compute_abi_info<'a, Ty, C>(cx: &C, fn_abi: &mut FnAbi<'a, Ty>, flavor: Flavor)
+pub(crate) struct X86Options {
+    pub flavor: Flavor,
+    pub regparm: Option<u32>,
+}
+
+pub(crate) fn compute_abi_info<'a, Ty, C>(cx: &C, fn_abi: &mut FnAbi<'a, Ty>, opts: X86Options)
 where
     Ty: TyAbiInterface<'a, C> + Copy,
     C: HasDataLayout + HasTargetSpec,
@@ -128,58 +133,77 @@ where
         }
     }
 
-    if flavor == Flavor::FastcallOrVectorcall {
-        // Mark arguments as InReg like clang does it,
-        // so our fastcall/vectorcall is compatible with C/C++ fastcall/vectorcall.
+    fill_inregs(cx, fn_abi, opts, false);
+}
 
-        // Clang reference: lib/CodeGen/TargetInfo.cpp
-        // See X86_32ABIInfo::shouldPrimitiveUseInReg(), X86_32ABIInfo::updateFreeRegs()
+pub(crate) fn fill_inregs<'a, Ty, C>(
+    cx: &C,
+    fn_abi: &mut FnAbi<'a, Ty>,
+    opts: X86Options,
+    rust_abi: bool,
+) where
+    Ty: TyAbiInterface<'a, C> + Copy,
+{
+    if opts.flavor != Flavor::FastcallOrVectorcall && opts.regparm.is_none_or(|x| x == 0) {
+        return;
+    }
+    // Mark arguments as InReg like clang does it,
+    // so our fastcall/vectorcall is compatible with C/C++ fastcall/vectorcall.
 
-        // IsSoftFloatABI is only set to true on ARM platforms,
-        // which in turn can't be x86?
+    // Clang reference: lib/CodeGen/TargetInfo.cpp
+    // See X86_32ABIInfo::shouldPrimitiveUseInReg(), X86_32ABIInfo::updateFreeRegs()
 
-        let mut free_regs = 2;
+    // IsSoftFloatABI is only set to true on ARM platforms,
+    // which in turn can't be x86?
 
-        for arg in fn_abi.args.iter_mut() {
-            let attrs = match arg.mode {
-                PassMode::Ignore
-                | PassMode::Indirect { attrs: _, meta_attrs: None, on_stack: _ } => {
-                    continue;
-                }
-                PassMode::Direct(ref mut attrs) => attrs,
-                PassMode::Pair(..)
-                | PassMode::Indirect { attrs: _, meta_attrs: Some(_), on_stack: _ }
-                | PassMode::Cast { .. } => {
-                    unreachable!("x86 shouldn't be passing arguments by {:?}", arg.mode)
-                }
-            };
+    // 2 for fastcall/vectorcall, regparm limited by 3 otherwise
+    let mut free_regs = opts.regparm.unwrap_or(2).into();
+
+    // For types generating PassMode::Cast, InRegs will not be set.
+    // Maybe, this is a FIXME
+    let has_casts = fn_abi.args.iter().any(|arg| matches!(arg.mode, PassMode::Cast { .. }));
+    if has_casts && rust_abi {
+        return;
+    }
 
-            // 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 unit.kind == RegKind::Float {
+    for arg in fn_abi.args.iter_mut() {
+        let attrs = match arg.mode {
+            PassMode::Ignore | PassMode::Indirect { attrs: _, meta_attrs: None, on_stack: _ } => {
                 continue;
             }
+            PassMode::Direct(ref mut attrs) => attrs,
+            PassMode::Pair(..)
+            | PassMode::Indirect { attrs: _, meta_attrs: Some(_), on_stack: _ }
+            | PassMode::Cast { .. } => {
+                unreachable!("x86 shouldn't be passing arguments by {:?}", arg.mode)
+            }
+        };
 
-            let size_in_regs = (arg.layout.size.bits() + 31) / 32;
+        // 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) {
+            continue;
+        }
 
-            if size_in_regs == 0 {
-                continue;
-            }
+        let size_in_regs = (arg.layout.size.bits() + 31) / 32;
 
-            if size_in_regs > free_regs {
-                break;
-            }
+        if size_in_regs == 0 {
+            continue;
+        }
 
-            free_regs -= size_in_regs;
+        if size_in_regs > free_regs {
+            break;
+        }
 
-            if arg.layout.size.bits() <= 32 && unit.kind == RegKind::Integer {
-                attrs.set(ArgAttribute::InReg);
-            }
+        free_regs -= size_in_regs;
 
-            if free_regs == 0 {
-                break;
-            }
+        if arg.layout.size.bits() <= 32 && unit.kind == RegKind::Integer {
+            attrs.set(ArgAttribute::InReg);
+        }
+
+        if free_regs == 0 {
+            break;
         }
     }
 }
diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs
index f4b45a08195..da5f1f551a3 100644
--- a/compiler/rustc_target/src/spec/mod.rs
+++ b/compiler/rustc_target/src/spec/mod.rs
@@ -2096,6 +2096,18 @@ pub trait HasWasmCAbiOpt {
     fn wasm_c_abi_opt(&self) -> WasmCAbi;
 }
 
+/// x86 (32-bit) abi options.
+#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
+pub struct X86Abi {
+    /// On x86-32 targets, the regparm N causes the compiler to pass arguments
+    /// in registers EAX, EDX, and ECX instead of on the stack.
+    pub regparm: Option<u32>,
+}
+
+pub trait HasX86AbiOpt {
+    fn x86_abi_opt(&self) -> X86Abi;
+}
+
 type StaticCow<T> = Cow<'static, T>;
 
 /// Optional aspects of a target specification.
diff --git a/src/doc/unstable-book/src/compiler-flags/regparm.md b/src/doc/unstable-book/src/compiler-flags/regparm.md
new file mode 100644
index 00000000000..8f311f091c0
--- /dev/null
+++ b/src/doc/unstable-book/src/compiler-flags/regparm.md
@@ -0,0 +1,20 @@
+# `regparm`
+
+The tracking issue for this feature is: https://github.com/rust-lang/rust/issues/131749.
+
+------------------------
+
+Option -Zregparm=N causes the compiler to pass N arguments
+in registers EAX, EDX, and ECX instead of on the stack for "C", "cdecl", and "stdcall" fn.
+It is UNSOUND to link together crates that use different values for this flag.
+It is only supported on `x86`.
+
+It is equivalent to [Clang]'s and [GCC]'s `-mregparm`.
+
+Supported values for this option are 0-3.
+
+[Clang]: https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-mregparm
+[GCC]: https://gcc.gnu.org/onlinedocs/gcc/x86-Function-Attributes.html#index-regparm-function-attribute_002c-x86
+
+Implementation details:
+For eligible arguments, llvm `inreg` attribute is set.
diff --git a/tests/codegen/regparm-inreg.rs b/tests/codegen/regparm-inreg.rs
new file mode 100644
index 00000000000..c8c647bcc87
--- /dev/null
+++ b/tests/codegen/regparm-inreg.rs
@@ -0,0 +1,125 @@
+// Checks how `regparm` flag works with different calling conventions:
+// marks function arguments as "inreg" like the C/C++ compilers for the platforms.
+// x86 only.
+
+//@ compile-flags: --target i686-unknown-linux-gnu -O -C no-prepopulate-passes
+//@ needs-llvm-components: x86
+
+//@ revisions:regparm0 regparm1 regparm2 regparm3
+//@[regparm0] compile-flags: -Zregparm=0
+//@[regparm1] compile-flags: -Zregparm=1
+//@[regparm2] compile-flags: -Zregparm=2
+//@[regparm3] compile-flags: -Zregparm=3
+
+#![crate_type = "lib"]
+#![no_core]
+#![feature(no_core, lang_items, repr_simd)]
+#[lang = "sized"]
+trait Sized {}
+#[lang = "copy"]
+trait Copy {}
+
+pub mod tests {
+    // regparm doesn't work for "fastcall" calling conv (only 2 inregs)
+    // CHECK: @f1(i32 inreg noundef %_1, i32 inreg noundef %_2, i32 noundef %_3)
+    #[no_mangle]
+    pub extern "fastcall" fn f1(_: i32, _: i32, _: i32) {}
+
+    // regparm0: @f3(i32 noundef %_1, i32 noundef %_2, i32 noundef %_3)
+    // regparm1: @f3(i32 inreg noundef %_1, i32 noundef %_2, i32 noundef %_3)
+    // regparm2: @f3(i32 inreg noundef %_1, i32 inreg noundef %_2, i32 noundef %_3)
+    // regparm3: @f3(i32 inreg noundef %_1, i32 inreg noundef %_2, i32 inreg noundef %_3)
+    #[no_mangle]
+    pub extern "C" fn f3(_: i32, _: i32, _: i32) {}
+
+    // regparm0: @f4(i32 noundef %_1, i32 noundef %_2, i32 noundef %_3)
+    // regparm1: @f4(i32 inreg noundef %_1, i32 noundef %_2, i32 noundef %_3)
+    // regparm2: @f4(i32 inreg noundef %_1, i32 inreg noundef %_2, i32 noundef %_3)
+    // regparm3: @f4(i32 inreg noundef %_1, i32 inreg noundef %_2, i32 inreg noundef %_3)
+    #[no_mangle]
+    pub extern "cdecl" fn f4(_: i32, _: i32, _: i32) {}
+
+    // regparm0: @f5(i32 noundef %_1, i32 noundef %_2, i32 noundef %_3)
+    // regparm1: @f5(i32 inreg noundef %_1, i32 noundef %_2, i32 noundef %_3)
+    // regparm2: @f5(i32 inreg noundef %_1, i32 inreg noundef %_2, i32 noundef %_3)
+    // regparm3: @f5(i32 inreg noundef %_1, i32 inreg noundef %_2, i32 inreg noundef %_3)
+    #[no_mangle]
+    pub extern "stdcall" fn f5(_: i32, _: i32, _: i32) {}
+
+    // regparm doesn't work for thiscall
+    // CHECK: @f6(i32 noundef %_1, i32 noundef %_2, i32 noundef %_3)
+    #[no_mangle]
+    pub extern "thiscall" fn f6(_: i32, _: i32, _: i32) {}
+
+    struct S1 {
+        x1: i32,
+    }
+    // regparm0: @f7(i32 noundef %_1, i32 noundef %_2, i32 noundef %_3, i32 noundef %_4)
+    // regparm1: @f7(i32 inreg noundef %_1, i32 noundef %_2, i32 noundef %_3, i32 noundef %_4)
+    // regparm2: @f7(i32 inreg noundef %_1, i32 inreg noundef %_2, i32 noundef %_3, i32 noundef %_4)
+    // regparm3: @f7(i32 inreg noundef %_1, i32 inreg noundef %_2, i32 inreg noundef %_3,
+    // regparm3-SAME: i32 noundef %_4)
+    #[no_mangle]
+    pub extern "C" fn f7(_: i32, _: i32, _: S1, _: i32) {}
+
+    #[repr(C)]
+    struct S2 {
+        x1: i32,
+        x2: i32,
+    }
+    // regparm0: @f8(i32 noundef %_1, i32 noundef %_2, ptr {{.*}} %_3, i32 noundef %_4)
+    // regparm1: @f8(i32 inreg noundef %_1, i32 noundef %_2, ptr {{.*}} %_3, i32 noundef %_4)
+    // regparm2: @f8(i32 inreg noundef %_1, i32 inreg noundef %_2, ptr {{.*}} %_3, i32 noundef %_4)
+    // regparm3: @f8(i32 inreg noundef %_1, i32 inreg noundef %_2, ptr {{.*}} %_3,
+    // regparm3-SAME: i32 inreg noundef %_4)
+    #[no_mangle]
+    pub extern "C" fn f8(_: i32, _: i32, _: S2, _: i32) {}
+
+    // regparm0: @f9(i1 noundef zeroext %_1, i16 noundef signext %_2, i64 noundef %_3,
+    // regparm0-SAME: i128 noundef %_4)
+    // regparm1: @f9(i1 inreg noundef zeroext %_1, i16 noundef signext %_2, i64 noundef %_3,
+    // regparm1-SAME: i128 noundef %_4)
+    // regparm2: @f9(i1 inreg noundef zeroext %_1, i16 inreg noundef signext %_2, i64 noundef %_3,
+    // regparm2-SAME: i128 noundef %_4)
+    // regparm3: @f9(i1 inreg noundef zeroext %_1, i16 inreg noundef signext %_2, i64 noundef %_3,
+    // regparm3-SAME: i128 noundef %_4)
+    #[no_mangle]
+    pub extern "C" fn f9(_: bool, _: i16, _: i64, _: u128) {}
+
+    // regparm0: @f10(float noundef %_1, double noundef %_2, i1 noundef zeroext %_3,
+    // regparm0-SAME: i16 noundef signext %_4)
+    // regparm1: @f10(float noundef %_1, double noundef %_2, i1 inreg noundef zeroext %_3,
+    // regparm1-SAME: i16 noundef signext %_4)
+    // regparm2: @f10(float noundef %_1, double noundef %_2, i1 inreg noundef zeroext %_3,
+    // regparm2-SAME: i16 inreg noundef signext %_4)
+    // regparm3: @f10(float noundef %_1, double noundef %_2, i1 inreg noundef zeroext %_3,
+    // regparm3-SAME: i16 inreg noundef signext %_4)
+    #[no_mangle]
+    pub extern "C" fn f10(_: f32, _: f64, _: bool, _: i16) {}
+
+    #[allow(non_camel_case_types)]
+    #[repr(simd)]
+    pub struct __m128([f32; 4]);
+
+    // regparm0: @f11(i32 noundef %_1, <4 x float> %_2, i32 noundef %_3, i32 noundef %_4)
+    // regparm1: @f11(i32 inreg noundef %_1, <4 x float> %_2, i32 noundef %_3, i32 noundef %_4)
+    // regparm2: @f11(i32 inreg noundef %_1, <4 x float> %_2, i32 inreg noundef %_3,
+    // regparm2-SAME: i32 noundef %_4)
+    // regparm3: @f11(i32 inreg noundef %_1, <4 x float> %_2, i32 inreg noundef %_3,
+    // regparm3-SAME: i32 inreg noundef %_4)
+    #[no_mangle]
+    pub extern "C" fn f11(_: i32, _: __m128, _: i32, _: i32) {}
+
+    #[allow(non_camel_case_types)]
+    #[repr(simd)]
+    pub struct __m256([f32; 8]);
+
+    // regparm0: @f12(i32 noundef %_1, <8 x float> %_2, i32 noundef %_3, i32 noundef %_4)
+    // regparm1: @f12(i32 inreg noundef %_1, <8 x float> %_2, i32 noundef %_3, i32 noundef %_4)
+    // regparm2: @f12(i32 inreg noundef %_1, <8 x float> %_2, i32 inreg noundef %_3,
+    // regparm2-SAME: i32 noundef %_4)
+    // regparm3: @f12(i32 inreg noundef %_1, <8 x float> %_2, i32 inreg noundef %_3,
+    // regparm3-SAME: i32 inreg noundef %_4)
+    #[no_mangle]
+    pub extern "C" fn f12(_: i32, _: __m256, _: i32, _: i32) {}
+}
diff --git a/tests/ui/invalid-compile-flags/regparm/regparm-valid-values.regparm4.stderr b/tests/ui/invalid-compile-flags/regparm/regparm-valid-values.regparm4.stderr
new file mode 100644
index 00000000000..8fc04adf57f
--- /dev/null
+++ b/tests/ui/invalid-compile-flags/regparm/regparm-valid-values.regparm4.stderr
@@ -0,0 +1,4 @@
+error: `-Zregparm=4` is unsupported (valid values 0-3)
+
+error: aborting due to 1 previous error
+
diff --git a/tests/ui/invalid-compile-flags/regparm/regparm-valid-values.rs b/tests/ui/invalid-compile-flags/regparm/regparm-valid-values.rs
new file mode 100644
index 00000000000..b548d678520
--- /dev/null
+++ b/tests/ui/invalid-compile-flags/regparm/regparm-valid-values.rs
@@ -0,0 +1,24 @@
+//@ revisions: regparm0 regparm1 regparm2 regparm3 regparm4
+
+//@ needs-llvm-components: x86
+//@ compile-flags: --target i686-unknown-linux-gnu
+
+//@[regparm0] check-pass
+//@[regparm0] compile-flags: -Zregparm=0
+
+//@[regparm1] check-pass
+//@[regparm1] compile-flags: -Zregparm=1
+
+//@[regparm2] check-pass
+//@[regparm2] compile-flags: -Zregparm=2
+
+//@[regparm3] check-pass
+//@[regparm3] compile-flags: -Zregparm=3
+
+//@[regparm4] check-fail
+//@[regparm4] compile-flags: -Zregparm=4
+//@[regparm4] error-pattern: `-Zregparm=4` is unsupported (valid values 0-3)
+
+#![feature(no_core)]
+#![no_core]
+#![no_main]
diff --git a/tests/ui/invalid-compile-flags/regparm/requires-x86.aarch64.stderr b/tests/ui/invalid-compile-flags/regparm/requires-x86.aarch64.stderr
new file mode 100644
index 00000000000..2433519f803
--- /dev/null
+++ b/tests/ui/invalid-compile-flags/regparm/requires-x86.aarch64.stderr
@@ -0,0 +1,4 @@
+error: `-Zregparm=N` is only supported on x86
+
+error: aborting due to 1 previous error
+
diff --git a/tests/ui/invalid-compile-flags/regparm/requires-x86.rs b/tests/ui/invalid-compile-flags/regparm/requires-x86.rs
new file mode 100644
index 00000000000..ce6e437fb47
--- /dev/null
+++ b/tests/ui/invalid-compile-flags/regparm/requires-x86.rs
@@ -0,0 +1,21 @@
+//@ revisions: x86 x86_64 aarch64
+
+//@ compile-flags: -Zregparm=3
+
+//@[x86] check-pass
+//@[x86] needs-llvm-components: x86
+//@[x86] compile-flags: --target i686-unknown-linux-gnu
+
+//@[x86_64] check-fail
+//@[x86_64] needs-llvm-components: x86
+//@[x86_64] compile-flags: --target x86_64-unknown-linux-gnu
+//@[x86_64] error-pattern: `-Zregparm=N` is only supported on x86
+
+//@[aarch64] check-fail
+//@[aarch64] needs-llvm-components: aarch64
+//@[aarch64] compile-flags: --target aarch64-unknown-linux-gnu
+//@[aarch64] error-pattern: `-Zregparm=N` is only supported on x86
+
+#![feature(no_core)]
+#![no_core]
+#![no_main]
diff --git a/tests/ui/invalid-compile-flags/regparm/requires-x86.x86_64.stderr b/tests/ui/invalid-compile-flags/regparm/requires-x86.x86_64.stderr
new file mode 100644
index 00000000000..2433519f803
--- /dev/null
+++ b/tests/ui/invalid-compile-flags/regparm/requires-x86.x86_64.stderr
@@ -0,0 +1,4 @@
+error: `-Zregparm=N` is only supported on x86
+
+error: aborting due to 1 previous error
+