diff --git a/compiler/rustc_error_codes/src/error_codes/E0798.md b/compiler/rustc_error_codes/src/error_codes/E0798.md
new file mode 100644
index 0000000000000..da08cde301000
--- /dev/null
+++ b/compiler/rustc_error_codes/src/error_codes/E0798.md
@@ -0,0 +1,39 @@
+Functions marked as `C-cmse-nonsecure-call` place restrictions on their
+inputs and outputs.
+
+- inputs must fit in the 4 available 32-bit argument registers. Alignment
+is relevant.
+- outputs must either fit in 4 bytes, or be a foundational type of
+size 8 (`i64`, `u64`, `f64`).
+- no generics can be used in the signature
+
+For more information,
+see [arm's aapcs32](https://github.com/ARM-software/abi-aa/releases).
+
+Erroneous code example:
+
+```ignore (only fails on supported targets)
+#![feature(abi_c_cmse_nonsecure_call)]
+
+#[no_mangle]
+pub fn test(
+    f: extern "C-cmse-nonsecure-call" fn(u32, u32, u32, u32, u32) -> u32,
+) -> u32 {
+    f(1, 2, 3, 4, 5)
+}
+```
+
+Arguments' alignment is respected. In the example below, padding is inserted
+so that the `u64` argument is passed in registers r2 and r3. There is then no
+room left for the final `f32` argument
+
+```ignore (only fails on supported targets)
+#![feature(abi_c_cmse_nonsecure_call)]
+
+#[no_mangle]
+pub fn test(
+    f: extern "C-cmse-nonsecure-call" fn(u32, u64, f32) -> u32,
+) -> u32 {
+    f(1, 2, 3.0)
+}
+```
diff --git a/compiler/rustc_error_codes/src/lib.rs b/compiler/rustc_error_codes/src/lib.rs
index d13d5e1bca219..2a7bc2501c081 100644
--- a/compiler/rustc_error_codes/src/lib.rs
+++ b/compiler/rustc_error_codes/src/lib.rs
@@ -536,6 +536,7 @@ E0794: 0794,
 E0795: 0795,
 E0796: 0796,
 E0797: 0797,
+E0798: 0798,
         );
     )
 }
diff --git a/compiler/rustc_hir_analysis/messages.ftl b/compiler/rustc_hir_analysis/messages.ftl
index 24c5377a3b125..9e5fa37de322c 100644
--- a/compiler/rustc_hir_analysis/messages.ftl
+++ b/compiler/rustc_hir_analysis/messages.ftl
@@ -58,6 +58,23 @@ hir_analysis_cannot_capture_late_bound_ty =
 hir_analysis_closure_implicit_hrtb = implicit types in closure signatures are forbidden when `for<...>` is present
     .label = `for<...>` is here
 
+hir_analysis_cmse_call_generic =
+    function pointers with the `"C-cmse-nonsecure-call"` ABI cannot contain generics in their type
+
+hir_analysis_cmse_call_inputs_stack_spill =
+    arguments for `"C-cmse-nonsecure-call"` function too large to pass via registers
+    .label = {$plural ->
+        [false] this argument doesn't
+        *[true] these arguments don't
+    } fit in the available registers
+    .note = functions with the `"C-cmse-nonsecure-call"` ABI must pass all their arguments via the 4 32-bit available argument registers
+
+hir_analysis_cmse_call_output_stack_spill =
+    return value of `"C-cmse-nonsecure-call"` function too large to pass via registers
+    .label = this type doesn't fit in the available registers
+    .note1 = functions with the `"C-cmse-nonsecure-call"` ABI must pass their result via the available return registers
+    .note2 = the result must either be a (transparently wrapped) i64, u64 or f64, or be at most 4 bytes in size
+
 hir_analysis_coerce_unsized_may = the trait `{$trait_name}` may only be implemented for a coercion between structures
 
 hir_analysis_coerce_unsized_multi = implementing the trait `CoerceUnsized` requires multiple coercions
diff --git a/compiler/rustc_hir_analysis/src/errors.rs b/compiler/rustc_hir_analysis/src/errors.rs
index 0ee87a13e9e37..a77f967a5caa3 100644
--- a/compiler/rustc_hir_analysis/src/errors.rs
+++ b/compiler/rustc_hir_analysis/src/errors.rs
@@ -1682,3 +1682,30 @@ pub struct InvalidReceiverTy<'tcx> {
 #[note]
 #[help]
 pub struct EffectsWithoutNextSolver;
+
+#[derive(Diagnostic)]
+#[diag(hir_analysis_cmse_call_inputs_stack_spill, code = E0798)]
+#[note]
+pub struct CmseCallInputsStackSpill {
+    #[primary_span]
+    #[label]
+    pub span: Span,
+    pub plural: bool,
+}
+
+#[derive(Diagnostic)]
+#[diag(hir_analysis_cmse_call_output_stack_spill, code = E0798)]
+#[note(hir_analysis_note1)]
+#[note(hir_analysis_note2)]
+pub struct CmseCallOutputStackSpill {
+    #[primary_span]
+    #[label]
+    pub span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(hir_analysis_cmse_call_generic, code = E0798)]
+pub struct CmseCallGeneric {
+    #[primary_span]
+    pub span: Span,
+}
diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/cmse.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/cmse.rs
new file mode 100644
index 0000000000000..e99717ce00f8c
--- /dev/null
+++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/cmse.rs
@@ -0,0 +1,156 @@
+use rustc_errors::DiagCtxtHandle;
+use rustc_hir as hir;
+use rustc_hir::HirId;
+use rustc_middle::ty::layout::LayoutError;
+use rustc_middle::ty::{self, ParamEnv, TyCtxt};
+use rustc_span::Span;
+use rustc_target::spec::abi;
+
+use crate::errors;
+
+/// Check conditions on inputs and outputs that the cmse ABIs impose: arguments and results MUST be
+/// returned via registers (i.e. MUST NOT spill to the stack). LLVM will also validate these
+/// conditions, but by checking them here rustc can emit nicer error messages.
+pub fn validate_cmse_abi<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    dcx: DiagCtxtHandle<'_>,
+    hir_id: HirId,
+    abi: abi::Abi,
+    fn_sig: ty::PolyFnSig<'tcx>,
+) {
+    if let abi::Abi::CCmseNonSecureCall = abi {
+        let hir_node = tcx.hir_node(hir_id);
+        let hir::Node::Ty(hir::Ty {
+            span: bare_fn_span,
+            kind: hir::TyKind::BareFn(bare_fn_ty),
+            ..
+        }) = hir_node
+        else {
+            // might happen when this ABI is used incorrectly. That will be handled elsewhere
+            return;
+        };
+
+        match is_valid_cmse_inputs(tcx, fn_sig) {
+            Ok(Ok(())) => {}
+            Ok(Err(index)) => {
+                // fn(x: u32, u32, u32, u16, y: u16) -> u32,
+                //                           ^^^^^^
+                let span = bare_fn_ty.param_names[index]
+                    .span
+                    .to(bare_fn_ty.decl.inputs[index].span)
+                    .to(bare_fn_ty.decl.inputs.last().unwrap().span);
+                let plural = bare_fn_ty.param_names.len() - index != 1;
+                dcx.emit_err(errors::CmseCallInputsStackSpill { span, plural });
+            }
+            Err(layout_err) => {
+                if let Some(err) = cmse_layout_err(layout_err, *bare_fn_span) {
+                    dcx.emit_err(err);
+                }
+            }
+        }
+
+        match is_valid_cmse_output(tcx, fn_sig) {
+            Ok(true) => {}
+            Ok(false) => {
+                let span = bare_fn_ty.decl.output.span();
+                dcx.emit_err(errors::CmseCallOutputStackSpill { span });
+            }
+            Err(layout_err) => {
+                if let Some(err) = cmse_layout_err(layout_err, *bare_fn_span) {
+                    dcx.emit_err(err);
+                }
+            }
+        };
+    }
+}
+
+/// Returns whether the inputs will fit into the available registers
+fn is_valid_cmse_inputs<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    fn_sig: ty::PolyFnSig<'tcx>,
+) -> Result<Result<(), usize>, &'tcx LayoutError<'tcx>> {
+    let mut span = None;
+    let mut accum = 0u64;
+
+    for (index, arg_def) in fn_sig.inputs().iter().enumerate() {
+        let layout = tcx.layout_of(ParamEnv::reveal_all().and(*arg_def.skip_binder()))?;
+
+        let align = layout.layout.align().abi.bytes();
+        let size = layout.layout.size().bytes();
+
+        accum += size;
+        accum = accum.next_multiple_of(Ord::max(4, align));
+
+        // i.e. exceeds 4 32-bit registers
+        if accum > 16 {
+            span = span.or(Some(index));
+        }
+    }
+
+    match span {
+        None => Ok(Ok(())),
+        Some(span) => Ok(Err(span)),
+    }
+}
+
+/// Returns whether the output will fit into the available registers
+fn is_valid_cmse_output<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    fn_sig: ty::PolyFnSig<'tcx>,
+) -> Result<bool, &'tcx LayoutError<'tcx>> {
+    let mut ret_ty = fn_sig.output().skip_binder();
+    let layout = tcx.layout_of(ParamEnv::reveal_all().and(ret_ty))?;
+    let size = layout.layout.size().bytes();
+
+    if size <= 4 {
+        return Ok(true);
+    } else if size > 8 {
+        return Ok(false);
+    }
+
+    // next we need to peel any repr(transparent) layers off
+    'outer: loop {
+        let ty::Adt(adt_def, args) = ret_ty.kind() else {
+            break;
+        };
+
+        if !adt_def.repr().transparent() {
+            break;
+        }
+
+        // the first field with non-trivial size and alignment must be the data
+        for variant_def in adt_def.variants() {
+            for field_def in variant_def.fields.iter() {
+                let ty = field_def.ty(tcx, args);
+                let layout = tcx.layout_of(ParamEnv::reveal_all().and(ty))?;
+
+                if !layout.layout.is_1zst() {
+                    ret_ty = ty;
+                    continue 'outer;
+                }
+            }
+        }
+    }
+
+    Ok(ret_ty == tcx.types.i64 || ret_ty == tcx.types.u64 || ret_ty == tcx.types.f64)
+}
+
+fn cmse_layout_err<'tcx>(
+    layout_err: &'tcx LayoutError<'tcx>,
+    span: Span,
+) -> Option<crate::errors::CmseCallGeneric> {
+    use LayoutError::*;
+
+    match layout_err {
+        Unknown(ty) => {
+            if ty.is_impl_trait() {
+                None // prevent double reporting of this error
+            } else {
+                Some(errors::CmseCallGeneric { span })
+            }
+        }
+        SizeOverflow(..) | NormalizationFailure(..) | ReferencesError(..) | Cycle(..) => {
+            None // not our job to report these
+        }
+    }
+}
diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs
index a665306f2c6a8..a632619aef205 100644
--- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs
+++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs
@@ -14,6 +14,7 @@
 //! trait references and bounds.
 
 mod bounds;
+mod cmse;
 pub mod errors;
 pub mod generics;
 mod lint;
@@ -2324,6 +2325,9 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
         let fn_ty = tcx.mk_fn_sig(input_tys, output_ty, decl.c_variadic, safety, abi);
         let bare_fn_ty = ty::Binder::bind_with_vars(fn_ty, bound_vars);
 
+        // reject function types that violate cmse ABI requirements
+        cmse::validate_cmse_abi(self.tcx(), self.dcx(), hir_id, abi, bare_fn_ty);
+
         // Find any late-bound regions declared in return type that do
         // not appear in the arguments. These are not well-formed.
         //
diff --git a/tests/ui/cmse-nonsecure/cmse-nonsecure-call/generics.rs b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/generics.rs
new file mode 100644
index 0000000000000..e6b0bf3e6860b
--- /dev/null
+++ b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/generics.rs
@@ -0,0 +1,21 @@
+//@ compile-flags: --target thumbv8m.main-none-eabi --crate-type lib
+//@ needs-llvm-components: arm
+#![feature(abi_c_cmse_nonsecure_call, no_core, lang_items)]
+#![no_core]
+#[lang = "sized"]
+pub trait Sized {}
+#[lang = "copy"]
+pub trait Copy {}
+impl Copy for u32 {}
+
+#[repr(C)]
+struct Wrapper<T>(T);
+
+struct Test<T: Copy> {
+    f1: extern "C-cmse-nonsecure-call" fn<U: Copy>(U, u32, u32, u32) -> u64, //~ ERROR cannot find type `U` in this scope
+    //~^ ERROR function pointer types may not have generic parameters
+    f2: extern "C-cmse-nonsecure-call" fn(impl Copy, u32, u32, u32) -> u64,
+    //~^ ERROR `impl Trait` is not allowed in `fn` pointer parameters
+    f3: extern "C-cmse-nonsecure-call" fn(T, u32, u32, u32) -> u64, //~ ERROR [E0798]
+    f4: extern "C-cmse-nonsecure-call" fn(Wrapper<T>, u32, u32, u32) -> u64, //~ ERROR [E0798]
+}
diff --git a/tests/ui/cmse-nonsecure/cmse-nonsecure-call/generics.stderr b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/generics.stderr
new file mode 100644
index 0000000000000..fa68d95218ccd
--- /dev/null
+++ b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/generics.stderr
@@ -0,0 +1,47 @@
+error: function pointer types may not have generic parameters
+  --> $DIR/generics.rs:15:42
+   |
+LL |     f1: extern "C-cmse-nonsecure-call" fn<U: Copy>(U, u32, u32, u32) -> u64,
+   |                                          ^^^^^^^^^
+
+error[E0412]: cannot find type `U` in this scope
+  --> $DIR/generics.rs:15:52
+   |
+LL | struct Test<T: Copy> {
+   |             - similarly named type parameter `T` defined here
+LL |     f1: extern "C-cmse-nonsecure-call" fn<U: Copy>(U, u32, u32, u32) -> u64,
+   |                                                    ^
+   |
+help: a type parameter with a similar name exists
+   |
+LL |     f1: extern "C-cmse-nonsecure-call" fn<U: Copy>(T, u32, u32, u32) -> u64,
+   |                                                    ~
+help: you might be missing a type parameter
+   |
+LL | struct Test<T: Copy, U> {
+   |                    +++
+
+error[E0562]: `impl Trait` is not allowed in `fn` pointer parameters
+  --> $DIR/generics.rs:17:43
+   |
+LL |     f2: extern "C-cmse-nonsecure-call" fn(impl Copy, u32, u32, u32) -> u64,
+   |                                           ^^^^^^^^^
+   |
+   = note: `impl Trait` is only allowed in arguments and return types of functions and methods
+
+error[E0798]: function pointers with the `"C-cmse-nonsecure-call"` ABI cannot contain generics in their type
+  --> $DIR/generics.rs:19:9
+   |
+LL |     f3: extern "C-cmse-nonsecure-call" fn(T, u32, u32, u32) -> u64,
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error[E0798]: function pointers with the `"C-cmse-nonsecure-call"` ABI cannot contain generics in their type
+  --> $DIR/generics.rs:20:9
+   |
+LL |     f4: extern "C-cmse-nonsecure-call" fn(Wrapper<T>, u32, u32, u32) -> u64,
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to 5 previous errors
+
+Some errors have detailed explanations: E0412, E0562, E0798.
+For more information about an error, try `rustc --explain E0412`.
diff --git a/tests/ui/cmse-nonsecure/cmse-nonsecure-call/params-on-registers.rs b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/params-on-registers.rs
deleted file mode 100644
index 364d0858afb96..0000000000000
--- a/tests/ui/cmse-nonsecure/cmse-nonsecure-call/params-on-registers.rs
+++ /dev/null
@@ -1,24 +0,0 @@
-//@ build-pass
-//@ compile-flags: --target thumbv8m.main-none-eabi --crate-type lib
-//@ needs-llvm-components: arm
-#![feature(abi_c_cmse_nonsecure_call, no_core, lang_items, intrinsics)]
-#![no_core]
-#[lang="sized"]
-pub trait Sized { }
-#[lang="copy"]
-pub trait Copy { }
-impl Copy for u32 {}
-
-extern "rust-intrinsic" {
-    pub fn transmute<T, U>(e: T) -> U;
-}
-
-#[no_mangle]
-pub fn test(a: u32, b: u32, c: u32, d: u32) -> u32 {
-    let non_secure_function = unsafe {
-        transmute::<usize, extern "C-cmse-nonsecure-call" fn(u32, u32, u32, u32) -> u32>(
-            0x10000004,
-        )
-    };
-    non_secure_function(a, b, c, d)
-}
diff --git a/tests/ui/cmse-nonsecure/cmse-nonsecure-call/params-on-stack.rs b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/params-on-stack.rs
deleted file mode 100644
index c225a26c065d6..0000000000000
--- a/tests/ui/cmse-nonsecure/cmse-nonsecure-call/params-on-stack.rs
+++ /dev/null
@@ -1,27 +0,0 @@
-//@ build-fail
-//@ compile-flags: --target thumbv8m.main-none-eabi --crate-type lib
-//@ needs-llvm-components: arm
-#![feature(abi_c_cmse_nonsecure_call, no_core, lang_items, intrinsics)]
-#![no_core]
-#[lang="sized"]
-pub trait Sized { }
-#[lang="copy"]
-pub trait Copy { }
-impl Copy for u32 {}
-
-extern "rust-intrinsic" {
-    pub fn transmute<T, U>(e: T) -> U;
-}
-
-#[no_mangle]
-pub fn test(a: u32, b: u32, c: u32, d: u32, e: u32) -> u32 {
-    let non_secure_function = unsafe {
-        transmute::<
-            usize,
-            extern "C-cmse-nonsecure-call" fn(u32, u32, u32, u32, u32) -> u32>
-        (
-            0x10000004,
-        )
-    };
-    non_secure_function(a, b, c, d, e)
-}
diff --git a/tests/ui/cmse-nonsecure/cmse-nonsecure-call/params-on-stack.stderr b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/params-on-stack.stderr
deleted file mode 100644
index a8aced2483e8f..0000000000000
--- a/tests/ui/cmse-nonsecure/cmse-nonsecure-call/params-on-stack.stderr
+++ /dev/null
@@ -1,4 +0,0 @@
-error: <unknown>:0:0: in function test i32 (i32, i32, i32, i32, i32): call to non-secure function would require passing arguments on stack
-
-error: aborting due to 1 previous error
-
diff --git a/tests/ui/cmse-nonsecure/cmse-nonsecure-call/params-via-stack.rs b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/params-via-stack.rs
new file mode 100644
index 0000000000000..083a563bd7ba7
--- /dev/null
+++ b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/params-via-stack.rs
@@ -0,0 +1,23 @@
+//@ compile-flags: --target thumbv8m.main-none-eabi --crate-type lib
+//@ needs-llvm-components: arm
+#![feature(abi_c_cmse_nonsecure_call, no_core, lang_items)]
+#![no_core]
+#[lang = "sized"]
+pub trait Sized {}
+#[lang = "copy"]
+pub trait Copy {}
+impl Copy for u32 {}
+
+#[repr(C, align(16))]
+#[allow(unused)]
+pub struct AlignRelevant(u32);
+
+#[no_mangle]
+pub fn test(
+    f1: extern "C-cmse-nonsecure-call" fn(u32, u32, u32, u32, x: u32, y: u32), //~ ERROR [E0798]
+    f2: extern "C-cmse-nonsecure-call" fn(u32, u32, u32, u16, u16),            //~ ERROR [E0798]
+    f3: extern "C-cmse-nonsecure-call" fn(u32, u64, u32),                      //~ ERROR [E0798]
+    f4: extern "C-cmse-nonsecure-call" fn(AlignRelevant, u32),                 //~ ERROR [E0798]
+    f5: extern "C-cmse-nonsecure-call" fn([u32; 5]),                           //~ ERROR [E0798]
+) {
+}
diff --git a/tests/ui/cmse-nonsecure/cmse-nonsecure-call/params-via-stack.stderr b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/params-via-stack.stderr
new file mode 100644
index 0000000000000..a5f5e1c3151d8
--- /dev/null
+++ b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/params-via-stack.stderr
@@ -0,0 +1,43 @@
+error[E0798]: arguments for `"C-cmse-nonsecure-call"` function too large to pass via registers
+  --> $DIR/params-via-stack.rs:17:63
+   |
+LL |     f1: extern "C-cmse-nonsecure-call" fn(u32, u32, u32, u32, x: u32, y: u32),
+   |                                                               ^^^^^^^^^^^^^^ these arguments don't fit in the available registers
+   |
+   = note: functions with the `"C-cmse-nonsecure-call"` ABI must pass all their arguments via the 4 32-bit available argument registers
+
+error[E0798]: arguments for `"C-cmse-nonsecure-call"` function too large to pass via registers
+  --> $DIR/params-via-stack.rs:18:63
+   |
+LL |     f2: extern "C-cmse-nonsecure-call" fn(u32, u32, u32, u16, u16),
+   |                                                               ^^^ this argument doesn't fit in the available registers
+   |
+   = note: functions with the `"C-cmse-nonsecure-call"` ABI must pass all their arguments via the 4 32-bit available argument registers
+
+error[E0798]: arguments for `"C-cmse-nonsecure-call"` function too large to pass via registers
+  --> $DIR/params-via-stack.rs:19:53
+   |
+LL |     f3: extern "C-cmse-nonsecure-call" fn(u32, u64, u32),
+   |                                                     ^^^ this argument doesn't fit in the available registers
+   |
+   = note: functions with the `"C-cmse-nonsecure-call"` ABI must pass all their arguments via the 4 32-bit available argument registers
+
+error[E0798]: arguments for `"C-cmse-nonsecure-call"` function too large to pass via registers
+  --> $DIR/params-via-stack.rs:20:58
+   |
+LL |     f4: extern "C-cmse-nonsecure-call" fn(AlignRelevant, u32),
+   |                                                          ^^^ this argument doesn't fit in the available registers
+   |
+   = note: functions with the `"C-cmse-nonsecure-call"` ABI must pass all their arguments via the 4 32-bit available argument registers
+
+error[E0798]: arguments for `"C-cmse-nonsecure-call"` function too large to pass via registers
+  --> $DIR/params-via-stack.rs:21:43
+   |
+LL |     f5: extern "C-cmse-nonsecure-call" fn([u32; 5]),
+   |                                           ^^^^^^^^ this argument doesn't fit in the available registers
+   |
+   = note: functions with the `"C-cmse-nonsecure-call"` ABI must pass all their arguments via the 4 32-bit available argument registers
+
+error: aborting due to 5 previous errors
+
+For more information about this error, try `rustc --explain E0798`.
diff --git a/tests/ui/cmse-nonsecure/cmse-nonsecure-call/return-via-stack.rs b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/return-via-stack.rs
new file mode 100644
index 0000000000000..e6af1d60e773f
--- /dev/null
+++ b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/return-via-stack.rs
@@ -0,0 +1,54 @@
+//@ compile-flags: --target thumbv8m.main-none-eabi --crate-type lib
+//@ needs-llvm-components: arm
+#![feature(abi_c_cmse_nonsecure_call, no_core, lang_items)]
+#![no_core]
+#[lang = "sized"]
+pub trait Sized {}
+#[lang = "copy"]
+pub trait Copy {}
+impl Copy for u32 {}
+
+#[repr(C)]
+pub struct ReprCU64(u64);
+
+#[repr(C)]
+pub struct ReprCBytes(u8, u8, u8, u8, u8);
+
+#[repr(C)]
+pub struct U64Compound(u32, u32);
+
+#[repr(C, align(16))]
+pub struct ReprCAlign16(u16);
+
+#[no_mangle]
+pub fn test(
+    f1: extern "C-cmse-nonsecure-call" fn() -> ReprCU64, //~ ERROR [E0798]
+    f2: extern "C-cmse-nonsecure-call" fn() -> ReprCBytes, //~ ERROR [E0798]
+    f3: extern "C-cmse-nonsecure-call" fn() -> U64Compound, //~ ERROR [E0798]
+    f4: extern "C-cmse-nonsecure-call" fn() -> ReprCAlign16, //~ ERROR [E0798]
+    f5: extern "C-cmse-nonsecure-call" fn() -> [u8; 5],  //~ ERROR [E0798]
+) {
+}
+
+#[allow(improper_ctypes_definitions)]
+struct Test {
+    u128: extern "C-cmse-nonsecure-call" fn() -> u128, //~ ERROR [E0798]
+    i128: extern "C-cmse-nonsecure-call" fn() -> i128, //~ ERROR [E0798]
+}
+
+#[repr(C)]
+pub union ReprCUnionU64 {
+    _unused: u64,
+}
+
+#[repr(Rust)]
+pub union ReprRustUnionU64 {
+    _unused: u64,
+}
+
+#[no_mangle]
+pub fn test_union(
+    f1: extern "C-cmse-nonsecure-call" fn() -> ReprRustUnionU64, //~ ERROR [E0798]
+    f2: extern "C-cmse-nonsecure-call" fn() -> ReprCUnionU64,    //~ ERROR [E0798]
+) {
+}
diff --git a/tests/ui/cmse-nonsecure/cmse-nonsecure-call/return-via-stack.stderr b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/return-via-stack.stderr
new file mode 100644
index 0000000000000..89f7f159d6e4d
--- /dev/null
+++ b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/return-via-stack.stderr
@@ -0,0 +1,84 @@
+error[E0798]: return value of `"C-cmse-nonsecure-call"` function too large to pass via registers
+  --> $DIR/return-via-stack.rs:35:50
+   |
+LL |     u128: extern "C-cmse-nonsecure-call" fn() -> u128,
+   |                                                  ^^^^ this type doesn't fit in the available registers
+   |
+   = note: functions with the `"C-cmse-nonsecure-call"` ABI must pass their result via the available return registers
+   = note: the result must either be a (transparently wrapped) i64, u64 or f64, or be at most 4 bytes in size
+
+error[E0798]: return value of `"C-cmse-nonsecure-call"` function too large to pass via registers
+  --> $DIR/return-via-stack.rs:36:50
+   |
+LL |     i128: extern "C-cmse-nonsecure-call" fn() -> i128,
+   |                                                  ^^^^ this type doesn't fit in the available registers
+   |
+   = note: functions with the `"C-cmse-nonsecure-call"` ABI must pass their result via the available return registers
+   = note: the result must either be a (transparently wrapped) i64, u64 or f64, or be at most 4 bytes in size
+
+error[E0798]: return value of `"C-cmse-nonsecure-call"` function too large to pass via registers
+  --> $DIR/return-via-stack.rs:25:48
+   |
+LL |     f1: extern "C-cmse-nonsecure-call" fn() -> ReprCU64,
+   |                                                ^^^^^^^^ this type doesn't fit in the available registers
+   |
+   = note: functions with the `"C-cmse-nonsecure-call"` ABI must pass their result via the available return registers
+   = note: the result must either be a (transparently wrapped) i64, u64 or f64, or be at most 4 bytes in size
+
+error[E0798]: return value of `"C-cmse-nonsecure-call"` function too large to pass via registers
+  --> $DIR/return-via-stack.rs:26:48
+   |
+LL |     f2: extern "C-cmse-nonsecure-call" fn() -> ReprCBytes,
+   |                                                ^^^^^^^^^^ this type doesn't fit in the available registers
+   |
+   = note: functions with the `"C-cmse-nonsecure-call"` ABI must pass their result via the available return registers
+   = note: the result must either be a (transparently wrapped) i64, u64 or f64, or be at most 4 bytes in size
+
+error[E0798]: return value of `"C-cmse-nonsecure-call"` function too large to pass via registers
+  --> $DIR/return-via-stack.rs:27:48
+   |
+LL |     f3: extern "C-cmse-nonsecure-call" fn() -> U64Compound,
+   |                                                ^^^^^^^^^^^ this type doesn't fit in the available registers
+   |
+   = note: functions with the `"C-cmse-nonsecure-call"` ABI must pass their result via the available return registers
+   = note: the result must either be a (transparently wrapped) i64, u64 or f64, or be at most 4 bytes in size
+
+error[E0798]: return value of `"C-cmse-nonsecure-call"` function too large to pass via registers
+  --> $DIR/return-via-stack.rs:28:48
+   |
+LL |     f4: extern "C-cmse-nonsecure-call" fn() -> ReprCAlign16,
+   |                                                ^^^^^^^^^^^^ this type doesn't fit in the available registers
+   |
+   = note: functions with the `"C-cmse-nonsecure-call"` ABI must pass their result via the available return registers
+   = note: the result must either be a (transparently wrapped) i64, u64 or f64, or be at most 4 bytes in size
+
+error[E0798]: return value of `"C-cmse-nonsecure-call"` function too large to pass via registers
+  --> $DIR/return-via-stack.rs:29:48
+   |
+LL |     f5: extern "C-cmse-nonsecure-call" fn() -> [u8; 5],
+   |                                                ^^^^^^^ this type doesn't fit in the available registers
+   |
+   = note: functions with the `"C-cmse-nonsecure-call"` ABI must pass their result via the available return registers
+   = note: the result must either be a (transparently wrapped) i64, u64 or f64, or be at most 4 bytes in size
+
+error[E0798]: return value of `"C-cmse-nonsecure-call"` function too large to pass via registers
+  --> $DIR/return-via-stack.rs:51:48
+   |
+LL |     f1: extern "C-cmse-nonsecure-call" fn() -> ReprRustUnionU64,
+   |                                                ^^^^^^^^^^^^^^^^ this type doesn't fit in the available registers
+   |
+   = note: functions with the `"C-cmse-nonsecure-call"` ABI must pass their result via the available return registers
+   = note: the result must either be a (transparently wrapped) i64, u64 or f64, or be at most 4 bytes in size
+
+error[E0798]: return value of `"C-cmse-nonsecure-call"` function too large to pass via registers
+  --> $DIR/return-via-stack.rs:52:48
+   |
+LL |     f2: extern "C-cmse-nonsecure-call" fn() -> ReprCUnionU64,
+   |                                                ^^^^^^^^^^^^^ this type doesn't fit in the available registers
+   |
+   = note: functions with the `"C-cmse-nonsecure-call"` ABI must pass their result via the available return registers
+   = note: the result must either be a (transparently wrapped) i64, u64 or f64, or be at most 4 bytes in size
+
+error: aborting due to 9 previous errors
+
+For more information about this error, try `rustc --explain E0798`.
diff --git a/tests/ui/cmse-nonsecure/cmse-nonsecure-call/via-registers.rs b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/via-registers.rs
new file mode 100644
index 0000000000000..9fda55c2a4807
--- /dev/null
+++ b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/via-registers.rs
@@ -0,0 +1,53 @@
+//@ build-pass
+//@ compile-flags: --target thumbv8m.main-none-eabi --crate-type lib
+//@ needs-llvm-components: arm
+#![feature(abi_c_cmse_nonsecure_call, no_core, lang_items, intrinsics)]
+#![no_core]
+#[lang = "sized"]
+pub trait Sized {}
+#[lang = "copy"]
+pub trait Copy {}
+impl Copy for u32 {}
+
+#[repr(transparent)]
+pub struct ReprTransparentStruct<T> {
+    _marker1: (),
+    _marker2: (),
+    field: T,
+    _marker3: (),
+}
+
+#[repr(transparent)]
+pub enum ReprTransparentEnumU64 {
+    A(u64),
+}
+
+#[repr(C)]
+pub struct U32Compound(u16, u16);
+
+#[no_mangle]
+#[allow(improper_ctypes_definitions)]
+pub fn params(
+    f1: extern "C-cmse-nonsecure-call" fn(),
+    f2: extern "C-cmse-nonsecure-call" fn(u32, u32, u32, u32),
+    f3: extern "C-cmse-nonsecure-call" fn(u64, u64),
+    f4: extern "C-cmse-nonsecure-call" fn(u128),
+    f5: extern "C-cmse-nonsecure-call" fn(f64, f32, f32),
+    f6: extern "C-cmse-nonsecure-call" fn(ReprTransparentStruct<u64>, U32Compound),
+    f7: extern "C-cmse-nonsecure-call" fn([u32; 4]),
+) {
+}
+
+#[no_mangle]
+pub fn returns(
+    f1: extern "C-cmse-nonsecure-call" fn() -> u32,
+    f2: extern "C-cmse-nonsecure-call" fn() -> u64,
+    f3: extern "C-cmse-nonsecure-call" fn() -> i64,
+    f4: extern "C-cmse-nonsecure-call" fn() -> f64,
+    f5: extern "C-cmse-nonsecure-call" fn() -> [u8; 4],
+    f6: extern "C-cmse-nonsecure-call" fn() -> ReprTransparentStruct<u64>,
+    f7: extern "C-cmse-nonsecure-call" fn() -> ReprTransparentStruct<ReprTransparentStruct<u64>>,
+    f8: extern "C-cmse-nonsecure-call" fn() -> ReprTransparentEnumU64,
+    f9: extern "C-cmse-nonsecure-call" fn() -> U32Compound,
+) {
+}