Skip to content

Commit 3b20150

Browse files
authored
Rollup merge of rust-lang#127814 - folkertdev:c-cmse-nonsecure-call-error-messages, r=oli-obk
`C-cmse-nonsecure-call`: improved error messages tracking issue: rust-lang#81391 issue for the error messages (partially implemented by this PR): rust-lang#81347 related, in that it also deals with CMSE: rust-lang#127766 When using the `C-cmse-nonsecure-call` ABI, both the arguments and return value must be passed via registers. Previously, when violating this constraint, an ugly LLVM error would be shown. Now, the rust compiler itself will print a pretty message and link to more information.
2 parents 6ae6f8b + c2894a4 commit 3b20150

File tree

16 files changed

+569
-55
lines changed

16 files changed

+569
-55
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
Functions marked as `C-cmse-nonsecure-call` place restrictions on their
2+
inputs and outputs.
3+
4+
- inputs must fit in the 4 available 32-bit argument registers. Alignment
5+
is relevant.
6+
- outputs must either fit in 4 bytes, or be a foundational type of
7+
size 8 (`i64`, `u64`, `f64`).
8+
- no generics can be used in the signature
9+
10+
For more information,
11+
see [arm's aapcs32](https://github.com/ARM-software/abi-aa/releases).
12+
13+
Erroneous code example:
14+
15+
```ignore (only fails on supported targets)
16+
#![feature(abi_c_cmse_nonsecure_call)]
17+
18+
#[no_mangle]
19+
pub fn test(
20+
f: extern "C-cmse-nonsecure-call" fn(u32, u32, u32, u32, u32) -> u32,
21+
) -> u32 {
22+
f(1, 2, 3, 4, 5)
23+
}
24+
```
25+
26+
Arguments' alignment is respected. In the example below, padding is inserted
27+
so that the `u64` argument is passed in registers r2 and r3. There is then no
28+
room left for the final `f32` argument
29+
30+
```ignore (only fails on supported targets)
31+
#![feature(abi_c_cmse_nonsecure_call)]
32+
33+
#[no_mangle]
34+
pub fn test(
35+
f: extern "C-cmse-nonsecure-call" fn(u32, u64, f32) -> u32,
36+
) -> u32 {
37+
f(1, 2, 3.0)
38+
}
39+
```

compiler/rustc_error_codes/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,7 @@ E0794: 0794,
536536
E0795: 0795,
537537
E0796: 0796,
538538
E0797: 0797,
539+
E0798: 0798,
539540
);
540541
)
541542
}

compiler/rustc_hir_analysis/messages.ftl

+17
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,23 @@ hir_analysis_cannot_capture_late_bound_ty =
5858
hir_analysis_closure_implicit_hrtb = implicit types in closure signatures are forbidden when `for<...>` is present
5959
.label = `for<...>` is here
6060
61+
hir_analysis_cmse_call_generic =
62+
function pointers with the `"C-cmse-nonsecure-call"` ABI cannot contain generics in their type
63+
64+
hir_analysis_cmse_call_inputs_stack_spill =
65+
arguments for `"C-cmse-nonsecure-call"` function too large to pass via registers
66+
.label = {$plural ->
67+
[false] this argument doesn't
68+
*[true] these arguments don't
69+
} fit in the available registers
70+
.note = functions with the `"C-cmse-nonsecure-call"` ABI must pass all their arguments via the 4 32-bit available argument registers
71+
72+
hir_analysis_cmse_call_output_stack_spill =
73+
return value of `"C-cmse-nonsecure-call"` function too large to pass via registers
74+
.label = this type doesn't fit in the available registers
75+
.note1 = functions with the `"C-cmse-nonsecure-call"` ABI must pass their result via the available return registers
76+
.note2 = the result must either be a (transparently wrapped) i64, u64 or f64, or be at most 4 bytes in size
77+
6178
hir_analysis_coerce_unsized_may = the trait `{$trait_name}` may only be implemented for a coercion between structures
6279
6380
hir_analysis_coerce_unsized_multi = implementing the trait `CoerceUnsized` requires multiple coercions

compiler/rustc_hir_analysis/src/errors.rs

+27
Original file line numberDiff line numberDiff line change
@@ -1673,3 +1673,30 @@ pub struct InvalidReceiverTy<'tcx> {
16731673
#[note]
16741674
#[help]
16751675
pub struct EffectsWithoutNextSolver;
1676+
1677+
#[derive(Diagnostic)]
1678+
#[diag(hir_analysis_cmse_call_inputs_stack_spill, code = E0798)]
1679+
#[note]
1680+
pub struct CmseCallInputsStackSpill {
1681+
#[primary_span]
1682+
#[label]
1683+
pub span: Span,
1684+
pub plural: bool,
1685+
}
1686+
1687+
#[derive(Diagnostic)]
1688+
#[diag(hir_analysis_cmse_call_output_stack_spill, code = E0798)]
1689+
#[note(hir_analysis_note1)]
1690+
#[note(hir_analysis_note2)]
1691+
pub struct CmseCallOutputStackSpill {
1692+
#[primary_span]
1693+
#[label]
1694+
pub span: Span,
1695+
}
1696+
1697+
#[derive(Diagnostic)]
1698+
#[diag(hir_analysis_cmse_call_generic, code = E0798)]
1699+
pub struct CmseCallGeneric {
1700+
#[primary_span]
1701+
pub span: Span,
1702+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
use rustc_errors::DiagCtxtHandle;
2+
use rustc_hir as hir;
3+
use rustc_hir::HirId;
4+
use rustc_middle::ty::layout::LayoutError;
5+
use rustc_middle::ty::{self, ParamEnv, TyCtxt};
6+
use rustc_span::Span;
7+
use rustc_target::spec::abi;
8+
9+
use crate::errors;
10+
11+
/// Check conditions on inputs and outputs that the cmse ABIs impose: arguments and results MUST be
12+
/// returned via registers (i.e. MUST NOT spill to the stack). LLVM will also validate these
13+
/// conditions, but by checking them here rustc can emit nicer error messages.
14+
pub fn validate_cmse_abi<'tcx>(
15+
tcx: TyCtxt<'tcx>,
16+
dcx: DiagCtxtHandle<'_>,
17+
hir_id: HirId,
18+
abi: abi::Abi,
19+
fn_sig: ty::PolyFnSig<'tcx>,
20+
) {
21+
if let abi::Abi::CCmseNonSecureCall = abi {
22+
let hir_node = tcx.hir_node(hir_id);
23+
let hir::Node::Ty(hir::Ty {
24+
span: bare_fn_span,
25+
kind: hir::TyKind::BareFn(bare_fn_ty),
26+
..
27+
}) = hir_node
28+
else {
29+
// might happen when this ABI is used incorrectly. That will be handled elsewhere
30+
return;
31+
};
32+
33+
match is_valid_cmse_inputs(tcx, fn_sig) {
34+
Ok(Ok(())) => {}
35+
Ok(Err(index)) => {
36+
// fn(x: u32, u32, u32, u16, y: u16) -> u32,
37+
// ^^^^^^
38+
let span = bare_fn_ty.param_names[index]
39+
.span
40+
.to(bare_fn_ty.decl.inputs[index].span)
41+
.to(bare_fn_ty.decl.inputs.last().unwrap().span);
42+
let plural = bare_fn_ty.param_names.len() - index != 1;
43+
dcx.emit_err(errors::CmseCallInputsStackSpill { span, plural });
44+
}
45+
Err(layout_err) => {
46+
if let Some(err) = cmse_layout_err(layout_err, *bare_fn_span) {
47+
dcx.emit_err(err);
48+
}
49+
}
50+
}
51+
52+
match is_valid_cmse_output(tcx, fn_sig) {
53+
Ok(true) => {}
54+
Ok(false) => {
55+
let span = bare_fn_ty.decl.output.span();
56+
dcx.emit_err(errors::CmseCallOutputStackSpill { span });
57+
}
58+
Err(layout_err) => {
59+
if let Some(err) = cmse_layout_err(layout_err, *bare_fn_span) {
60+
dcx.emit_err(err);
61+
}
62+
}
63+
};
64+
}
65+
}
66+
67+
/// Returns whether the inputs will fit into the available registers
68+
fn is_valid_cmse_inputs<'tcx>(
69+
tcx: TyCtxt<'tcx>,
70+
fn_sig: ty::PolyFnSig<'tcx>,
71+
) -> Result<Result<(), usize>, &'tcx LayoutError<'tcx>> {
72+
let mut span = None;
73+
let mut accum = 0u64;
74+
75+
for (index, arg_def) in fn_sig.inputs().iter().enumerate() {
76+
let layout = tcx.layout_of(ParamEnv::reveal_all().and(*arg_def.skip_binder()))?;
77+
78+
let align = layout.layout.align().abi.bytes();
79+
let size = layout.layout.size().bytes();
80+
81+
accum += size;
82+
accum = accum.next_multiple_of(Ord::max(4, align));
83+
84+
// i.e. exceeds 4 32-bit registers
85+
if accum > 16 {
86+
span = span.or(Some(index));
87+
}
88+
}
89+
90+
match span {
91+
None => Ok(Ok(())),
92+
Some(span) => Ok(Err(span)),
93+
}
94+
}
95+
96+
/// Returns whether the output will fit into the available registers
97+
fn is_valid_cmse_output<'tcx>(
98+
tcx: TyCtxt<'tcx>,
99+
fn_sig: ty::PolyFnSig<'tcx>,
100+
) -> Result<bool, &'tcx LayoutError<'tcx>> {
101+
let mut ret_ty = fn_sig.output().skip_binder();
102+
let layout = tcx.layout_of(ParamEnv::reveal_all().and(ret_ty))?;
103+
let size = layout.layout.size().bytes();
104+
105+
if size <= 4 {
106+
return Ok(true);
107+
} else if size > 8 {
108+
return Ok(false);
109+
}
110+
111+
// next we need to peel any repr(transparent) layers off
112+
'outer: loop {
113+
let ty::Adt(adt_def, args) = ret_ty.kind() else {
114+
break;
115+
};
116+
117+
if !adt_def.repr().transparent() {
118+
break;
119+
}
120+
121+
// the first field with non-trivial size and alignment must be the data
122+
for variant_def in adt_def.variants() {
123+
for field_def in variant_def.fields.iter() {
124+
let ty = field_def.ty(tcx, args);
125+
let layout = tcx.layout_of(ParamEnv::reveal_all().and(ty))?;
126+
127+
if !layout.layout.is_1zst() {
128+
ret_ty = ty;
129+
continue 'outer;
130+
}
131+
}
132+
}
133+
}
134+
135+
Ok(ret_ty == tcx.types.i64 || ret_ty == tcx.types.u64 || ret_ty == tcx.types.f64)
136+
}
137+
138+
fn cmse_layout_err<'tcx>(
139+
layout_err: &'tcx LayoutError<'tcx>,
140+
span: Span,
141+
) -> Option<crate::errors::CmseCallGeneric> {
142+
use LayoutError::*;
143+
144+
match layout_err {
145+
Unknown(ty) => {
146+
if ty.is_impl_trait() {
147+
None // prevent double reporting of this error
148+
} else {
149+
Some(errors::CmseCallGeneric { span })
150+
}
151+
}
152+
SizeOverflow(..) | NormalizationFailure(..) | ReferencesError(..) | Cycle(..) => {
153+
None // not our job to report these
154+
}
155+
}
156+
}

compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs

+4
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
//! trait references and bounds.
1515
1616
mod bounds;
17+
mod cmse;
1718
pub mod errors;
1819
pub mod generics;
1920
mod lint;
@@ -2378,6 +2379,9 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
23782379
let fn_ty = tcx.mk_fn_sig(input_tys, output_ty, decl.c_variadic, safety, abi);
23792380
let bare_fn_ty = ty::Binder::bind_with_vars(fn_ty, bound_vars);
23802381

2382+
// reject function types that violate cmse ABI requirements
2383+
cmse::validate_cmse_abi(self.tcx(), self.dcx(), hir_id, abi, bare_fn_ty);
2384+
23812385
// Find any late-bound regions declared in return type that do
23822386
// not appear in the arguments. These are not well-formed.
23832387
//
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//@ compile-flags: --target thumbv8m.main-none-eabi --crate-type lib
2+
//@ needs-llvm-components: arm
3+
#![feature(abi_c_cmse_nonsecure_call, no_core, lang_items)]
4+
#![no_core]
5+
#[lang = "sized"]
6+
pub trait Sized {}
7+
#[lang = "copy"]
8+
pub trait Copy {}
9+
impl Copy for u32 {}
10+
11+
#[repr(C)]
12+
struct Wrapper<T>(T);
13+
14+
struct Test<T: Copy> {
15+
f1: extern "C-cmse-nonsecure-call" fn<U: Copy>(U, u32, u32, u32) -> u64, //~ ERROR cannot find type `U` in this scope
16+
//~^ ERROR function pointer types may not have generic parameters
17+
f2: extern "C-cmse-nonsecure-call" fn(impl Copy, u32, u32, u32) -> u64,
18+
//~^ ERROR `impl Trait` is not allowed in `fn` pointer parameters
19+
f3: extern "C-cmse-nonsecure-call" fn(T, u32, u32, u32) -> u64, //~ ERROR [E0798]
20+
f4: extern "C-cmse-nonsecure-call" fn(Wrapper<T>, u32, u32, u32) -> u64, //~ ERROR [E0798]
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
error: function pointer types may not have generic parameters
2+
--> $DIR/generics.rs:15:42
3+
|
4+
LL | f1: extern "C-cmse-nonsecure-call" fn<U: Copy>(U, u32, u32, u32) -> u64,
5+
| ^^^^^^^^^
6+
7+
error[E0412]: cannot find type `U` in this scope
8+
--> $DIR/generics.rs:15:52
9+
|
10+
LL | struct Test<T: Copy> {
11+
| - similarly named type parameter `T` defined here
12+
LL | f1: extern "C-cmse-nonsecure-call" fn<U: Copy>(U, u32, u32, u32) -> u64,
13+
| ^
14+
|
15+
help: a type parameter with a similar name exists
16+
|
17+
LL | f1: extern "C-cmse-nonsecure-call" fn<U: Copy>(T, u32, u32, u32) -> u64,
18+
| ~
19+
help: you might be missing a type parameter
20+
|
21+
LL | struct Test<T: Copy, U> {
22+
| +++
23+
24+
error[E0562]: `impl Trait` is not allowed in `fn` pointer parameters
25+
--> $DIR/generics.rs:17:43
26+
|
27+
LL | f2: extern "C-cmse-nonsecure-call" fn(impl Copy, u32, u32, u32) -> u64,
28+
| ^^^^^^^^^
29+
|
30+
= note: `impl Trait` is only allowed in arguments and return types of functions and methods
31+
32+
error[E0798]: function pointers with the `"C-cmse-nonsecure-call"` ABI cannot contain generics in their type
33+
--> $DIR/generics.rs:19:9
34+
|
35+
LL | f3: extern "C-cmse-nonsecure-call" fn(T, u32, u32, u32) -> u64,
36+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
37+
38+
error[E0798]: function pointers with the `"C-cmse-nonsecure-call"` ABI cannot contain generics in their type
39+
--> $DIR/generics.rs:20:9
40+
|
41+
LL | f4: extern "C-cmse-nonsecure-call" fn(Wrapper<T>, u32, u32, u32) -> u64,
42+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
43+
44+
error: aborting due to 5 previous errors
45+
46+
Some errors have detailed explanations: E0412, E0562, E0798.
47+
For more information about an error, try `rustc --explain E0412`.

tests/ui/cmse-nonsecure/cmse-nonsecure-call/params-on-registers.rs

-24
This file was deleted.

0 commit comments

Comments
 (0)