Skip to content

Commit a78b04c

Browse files
committed
move CMSE validation to hir_analysis
1 parent 8a3dd7f commit a78b04c

File tree

13 files changed

+363
-197
lines changed

13 files changed

+363
-197
lines changed

compiler/rustc_error_codes/src/error_codes/E0798.md

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ inputs and outputs.
55
is relevant.
66
- outputs must either fit in 4 bytes, or be a foundational type of
77
size 8 (`i64`, `u64`, `f64`).
8+
- no generics can be used in the signature
89

910
For more information,
1011
see [arm's aapcs32](https://github.com/ARM-software/abi-aa/releases).

compiler/rustc_hir_analysis/messages.ftl

+13
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,19 @@ 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 = these arguments don't fit in the available registers
67+
.note = functions with the `"C-cmse-nonsecure-call"` ABI must pass all their arguments via the 4 32-bit available argument registers
68+
69+
hir_analysis_cmse_call_output_stack_spill =
70+
return value of `"C-cmse-nonsecure-call"` function too large to pass via registers
71+
.label = this type doesn't fit in the available registers
72+
.note = functions with the `"C-cmse-nonsecure-call"` ABI must pass their result via the available return registers
73+
6174
hir_analysis_coerce_unsized_may = the trait `{$trait_name}` may only be implemented for a coercion between structures
6275
6376
hir_analysis_coerce_unsized_multi = implementing the trait `CoerceUnsized` requires multiple coercions

compiler/rustc_hir_analysis/src/check/check.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -1287,9 +1287,10 @@ pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, adt: ty::AdtDef<'tcx>)
12871287
(span, trivial, check_non_exhaustive(tcx, ty).break_value())
12881288
});
12891289

1290-
let non_trivial_fields = field_infos
1291-
.clone()
1292-
.filter_map(|(span, trivial, _non_exhaustive)| if !trivial { Some(span) } else { None });
1290+
let non_trivial_fields =
1291+
field_infos.clone().filter_map(
1292+
|(span, trivial, _non_exhaustive)| if !trivial { Some(span) } else { None },
1293+
);
12931294
let non_trivial_count = non_trivial_fields.clone().count();
12941295
if non_trivial_count >= 2 {
12951296
bad_non_zero_sized_fields(

compiler/rustc_hir_analysis/src/errors.rs

+25
Original file line numberDiff line numberDiff line change
@@ -1682,3 +1682,28 @@ pub struct InvalidReceiverTy<'tcx> {
16821682
#[note]
16831683
#[help]
16841684
pub struct EffectsWithoutNextSolver;
1685+
1686+
#[derive(Diagnostic)]
1687+
#[diag(hir_analysis_cmse_call_inputs_stack_spill, code = E0798)]
1688+
#[note]
1689+
pub struct CmseCallInputsStackSpill {
1690+
#[primary_span]
1691+
#[label]
1692+
pub span: Span,
1693+
}
1694+
1695+
#[derive(Diagnostic)]
1696+
#[diag(hir_analysis_cmse_call_output_stack_spill, code = E0798)]
1697+
#[note]
1698+
pub struct CmseCallOutputStackSpill {
1699+
#[primary_span]
1700+
#[label]
1701+
pub span: Span,
1702+
}
1703+
1704+
#[derive(Diagnostic)]
1705+
#[diag(hir_analysis_cmse_call_generic, code = E0798)]
1706+
pub struct CmseCallGeneric {
1707+
#[primary_span]
1708+
pub span: Span,
1709+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
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+
// fn(u32, u32, u32, u16, u16) -> u32,
34+
// ^^^^^^^^^^^^^^^^^^^^^^^ ^^^
35+
let output_span = bare_fn_ty.decl.output.span();
36+
let inputs_span = match (
37+
bare_fn_ty.param_names.first(),
38+
bare_fn_ty.decl.inputs.first(),
39+
bare_fn_ty.decl.inputs.last(),
40+
) {
41+
(Some(ident), Some(ty1), Some(ty2)) => ident.span.to(ty1.span).to(ty2.span),
42+
_ => *bare_fn_span,
43+
};
44+
45+
match is_valid_cmse_inputs(tcx, fn_sig) {
46+
Ok(true) => {}
47+
Ok(false) => {
48+
dcx.emit_err(errors::CmseCallInputsStackSpill { span: inputs_span });
49+
}
50+
Err(layout_err) => {
51+
if let Some(err) = cmse_layout_err(layout_err, inputs_span) {
52+
dcx.emit_err(err);
53+
}
54+
}
55+
}
56+
57+
match is_valid_cmse_output(tcx, fn_sig) {
58+
Ok(true) => {}
59+
Ok(false) => {
60+
dcx.emit_err(errors::CmseCallOutputStackSpill { span: output_span });
61+
}
62+
Err(layout_err) => {
63+
if let Some(err) = cmse_layout_err(layout_err, output_span) {
64+
dcx.emit_err(err);
65+
}
66+
}
67+
};
68+
}
69+
}
70+
71+
/// Returns whether the inputs will fit into the available registers
72+
fn is_valid_cmse_inputs<'tcx>(
73+
tcx: TyCtxt<'tcx>,
74+
fn_sig: ty::PolyFnSig<'tcx>,
75+
) -> Result<bool, &'tcx LayoutError<'tcx>> {
76+
let mut accum = 0u64;
77+
78+
for arg_def in fn_sig.inputs().iter() {
79+
let layout = tcx.layout_of(ParamEnv::reveal_all().and(*arg_def.skip_binder()))?;
80+
81+
let align = layout.layout.align().abi.bytes();
82+
let size = layout.layout.size().bytes();
83+
84+
accum += size;
85+
accum = accum.next_multiple_of(Ord::max(4, align));
86+
}
87+
88+
// i.e. 4 32-bit registers
89+
Ok(accum <= 16)
90+
}
91+
92+
/// Returns whether the output will fit into the available registers
93+
fn is_valid_cmse_output<'tcx>(
94+
tcx: TyCtxt<'tcx>,
95+
fn_sig: ty::PolyFnSig<'tcx>,
96+
) -> Result<bool, &'tcx LayoutError<'tcx>> {
97+
let mut ret_ty = fn_sig.output().skip_binder();
98+
let layout = tcx.layout_of(ParamEnv::reveal_all().and(ret_ty))?;
99+
let size = layout.layout.size().bytes();
100+
101+
if size <= 4 {
102+
return Ok(true);
103+
} else if size > 8 {
104+
return Ok(false);
105+
}
106+
107+
// next we need to peel any repr(transparent) layers off
108+
'outer: loop {
109+
let ty::Adt(adt_def, args) = ret_ty.kind() else {
110+
break;
111+
};
112+
113+
if !adt_def.repr().transparent() {
114+
break;
115+
}
116+
117+
// the first field with non-trivial size and alignment must be the data
118+
for variant_def in adt_def.variants() {
119+
for field_def in variant_def.fields.iter() {
120+
let ty = field_def.ty(tcx, args);
121+
let layout = tcx.layout_of(ParamEnv::reveal_all().and(ty))?;
122+
123+
if !layout.layout.is_1zst() {
124+
ret_ty = ty;
125+
continue 'outer;
126+
}
127+
}
128+
}
129+
}
130+
131+
Ok(ret_ty == tcx.types.i64 || ret_ty == tcx.types.u64 || ret_ty == tcx.types.f64)
132+
}
133+
134+
fn cmse_layout_err<'tcx>(
135+
layout_err: &'tcx LayoutError<'tcx>,
136+
span: Span,
137+
) -> Option<crate::errors::CmseCallGeneric> {
138+
use LayoutError::*;
139+
140+
match layout_err {
141+
Unknown(ty) => {
142+
if ty.is_impl_trait() {
143+
None // prevent double reporting of this error
144+
} else {
145+
Some(errors::CmseCallGeneric { span })
146+
}
147+
}
148+
SizeOverflow(..) | NormalizationFailure(..) | ReferencesError(..) | Cycle(..) => {
149+
None // not our job to report these
150+
}
151+
}
152+
}

compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs

+9-1
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;
@@ -1748,7 +1749,11 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
17481749
generic_segments.iter().map(|GenericPathSegment(_, index)| index).collect();
17491750
let _ = self.prohibit_generic_args(
17501751
path.segments.iter().enumerate().filter_map(|(index, seg)| {
1751-
if !indices.contains(&index) { Some(seg) } else { None }
1752+
if !indices.contains(&index) {
1753+
Some(seg)
1754+
} else {
1755+
None
1756+
}
17521757
}),
17531758
GenericsArgsErrExtend::DefVariant,
17541759
);
@@ -2324,6 +2329,9 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
23242329
let fn_ty = tcx.mk_fn_sig(input_tys, output_ty, decl.c_variadic, safety, abi);
23252330
let bare_fn_ty = ty::Binder::bind_with_vars(fn_ty, bound_vars);
23262331

2332+
// reject function types that violate cmse ABI requirements
2333+
cmse::validate_cmse_abi(self.tcx(), &self.dcx(), hir_id, abi, bare_fn_ty);
2334+
23272335
// Find any late-bound regions declared in return type that do
23282336
// not appear in the arguments. These are not well-formed.
23292337
//
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:14: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:14: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:16: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:18:43
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:19:43
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`.
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
//@ build-fail
21
//@ compile-flags: --target thumbv8m.main-none-eabi --crate-type lib
32
//@ needs-llvm-components: arm
4-
#![feature(abi_c_cmse_nonsecure_call, no_core, lang_items, intrinsics)]
3+
#![feature(abi_c_cmse_nonsecure_call, no_core, lang_items)]
54
#![no_core]
65
#[lang = "sized"]
76
pub trait Sized {}
@@ -15,15 +14,10 @@ pub struct AlignRelevant(u32);
1514

1615
#[no_mangle]
1716
pub fn test(
18-
f1: extern "C-cmse-nonsecure-call" fn(u32, u32, u32, u32, u32) -> u32,
19-
f2: extern "C-cmse-nonsecure-call" fn(u32, u32, u32, u16, u16) -> u32,
20-
f3: extern "C-cmse-nonsecure-call" fn(u32, u64, u32) -> u32,
21-
f4: extern "C-cmse-nonsecure-call" fn(AlignRelevant, u32) -> u32,
22-
f5: extern "C-cmse-nonsecure-call" fn([u32; 5]) -> u32,
17+
f1: extern "C-cmse-nonsecure-call" fn(u32, u32, u32, u32, u32), //~ ERROR [E0798]
18+
f2: extern "C-cmse-nonsecure-call" fn(u32, u32, u32, u16, u16), //~ ERROR [E0798]
19+
f3: extern "C-cmse-nonsecure-call" fn(u32, u64, u32), //~ ERROR [E0798]
20+
f4: extern "C-cmse-nonsecure-call" fn(AlignRelevant, u32), //~ ERROR [E0798]
21+
f5: extern "C-cmse-nonsecure-call" fn([u32; 5]), //~ ERROR [E0798]
2322
) {
24-
f1(1, 2, 3, 4, 5); //~ ERROR [E0798]
25-
f2(1, 2, 3, 4, 5); //~ ERROR [E0798]
26-
f3(1, 2, 3); //~ ERROR [E0798]
27-
f4(AlignRelevant(1), 2); //~ ERROR [E0798]
28-
f5([0xAA; 5]); //~ ERROR [E0798]
2923
}

0 commit comments

Comments
 (0)