rustc_hir_analysis/hir_ty_lowering/
cmse.rs

1use rustc_abi::{BackendRepr, ExternAbi, Float, Integer, Primitive, Scalar};
2use rustc_errors::{DiagCtxtHandle, E0781, struct_span_code_err};
3use rustc_hir::{self as hir, HirId};
4use rustc_middle::bug;
5use rustc_middle::ty::layout::{LayoutError, TyAndLayout};
6use rustc_middle::ty::{self, TyCtxt, TypeVisitableExt};
7
8use crate::errors;
9
10/// Check conditions on inputs and outputs that the cmse ABIs impose: arguments and results MUST be
11/// returned via registers (i.e. MUST NOT spill to the stack). LLVM will also validate these
12/// conditions, but by checking them here rustc can emit nicer error messages.
13pub(crate) fn validate_cmse_abi<'tcx>(
14    tcx: TyCtxt<'tcx>,
15    dcx: DiagCtxtHandle<'_>,
16    hir_id: HirId,
17    abi: ExternAbi,
18    fn_sig: ty::PolyFnSig<'tcx>,
19) {
20    match abi {
21        ExternAbi::CmseNonSecureCall => {
22            let hir_node = tcx.hir_node(hir_id);
23            let hir::Node::Ty(hir::Ty {
24                span: fn_ptr_span,
25                kind: hir::TyKind::FnPtr(fn_ptr_ty),
26                ..
27            }) = hir_node
28            else {
29                let span = match tcx.parent_hir_node(hir_id) {
30                    hir::Node::Item(hir::Item {
31                        kind: hir::ItemKind::ForeignMod { .. },
32                        span,
33                        ..
34                    }) => *span,
35                    _ => tcx.hir_span(hir_id),
36                };
37                struct_span_code_err!(
38                    dcx,
39                    span,
40                    E0781,
41                    "the `\"cmse-nonsecure-call\"` ABI is only allowed on function pointers"
42                )
43                .emit();
44                return;
45            };
46
47            match is_valid_cmse_inputs(tcx, fn_sig) {
48                Ok(Ok(())) => {}
49                Ok(Err(index)) => {
50                    // fn(x: u32, u32, u32, u16, y: u16) -> u32,
51                    //                           ^^^^^^
52                    let span = if let Some(ident) = fn_ptr_ty.param_idents[index] {
53                        ident.span.to(fn_ptr_ty.decl.inputs[index].span)
54                    } else {
55                        fn_ptr_ty.decl.inputs[index].span
56                    }
57                    .to(fn_ptr_ty.decl.inputs.last().unwrap().span);
58                    let plural = fn_ptr_ty.param_idents.len() - index != 1;
59                    dcx.emit_err(errors::CmseInputsStackSpill { span, plural, abi });
60                }
61                Err(layout_err) => {
62                    if should_emit_generic_error(abi, layout_err) {
63                        dcx.emit_err(errors::CmseCallGeneric { span: *fn_ptr_span });
64                    }
65                }
66            }
67
68            match is_valid_cmse_output(tcx, fn_sig) {
69                Ok(true) => {}
70                Ok(false) => {
71                    let span = fn_ptr_ty.decl.output.span();
72                    dcx.emit_err(errors::CmseOutputStackSpill { span, abi });
73                }
74                Err(layout_err) => {
75                    if should_emit_generic_error(abi, layout_err) {
76                        dcx.emit_err(errors::CmseCallGeneric { span: *fn_ptr_span });
77                    }
78                }
79            };
80        }
81        ExternAbi::CmseNonSecureEntry => {
82            let hir_node = tcx.hir_node(hir_id);
83            let Some(hir::FnSig { decl, span: fn_sig_span, .. }) = hir_node.fn_sig() else {
84                // might happen when this ABI is used incorrectly. That will be handled elsewhere
85                return;
86            };
87
88            // An `extern "cmse-nonsecure-entry"` function cannot be c-variadic. We run
89            // into https://github.com/rust-lang/rust/issues/132142 if we don't explicitly bail.
90            if decl.c_variadic {
91                return;
92            }
93
94            match is_valid_cmse_inputs(tcx, fn_sig) {
95                Ok(Ok(())) => {}
96                Ok(Err(index)) => {
97                    // fn f(x: u32, y: u32, z: u32, w: u16, q: u16) -> u32,
98                    //                                      ^^^^^^
99                    let span = decl.inputs[index].span.to(decl.inputs.last().unwrap().span);
100                    let plural = decl.inputs.len() - index != 1;
101                    dcx.emit_err(errors::CmseInputsStackSpill { span, plural, abi });
102                }
103                Err(layout_err) => {
104                    if should_emit_generic_error(abi, layout_err) {
105                        dcx.emit_err(errors::CmseEntryGeneric { span: *fn_sig_span });
106                    }
107                }
108            }
109
110            match is_valid_cmse_output(tcx, fn_sig) {
111                Ok(true) => {}
112                Ok(false) => {
113                    let span = decl.output.span();
114                    dcx.emit_err(errors::CmseOutputStackSpill { span, abi });
115                }
116                Err(layout_err) => {
117                    if should_emit_generic_error(abi, layout_err) {
118                        dcx.emit_err(errors::CmseEntryGeneric { span: *fn_sig_span });
119                    }
120                }
121            };
122        }
123        _ => (),
124    }
125}
126
127/// Returns whether the inputs will fit into the available registers
128fn is_valid_cmse_inputs<'tcx>(
129    tcx: TyCtxt<'tcx>,
130    fn_sig: ty::PolyFnSig<'tcx>,
131) -> Result<Result<(), usize>, &'tcx LayoutError<'tcx>> {
132    let mut span = None;
133    let mut accum = 0u64;
134
135    // this type is only used for layout computation, which does not rely on regions
136    let fn_sig = tcx.instantiate_bound_regions_with_erased(fn_sig);
137    let fn_sig = tcx.erase_and_anonymize_regions(fn_sig);
138
139    for (index, ty) in fn_sig.inputs().iter().enumerate() {
140        let layout = tcx.layout_of(ty::TypingEnv::fully_monomorphized().as_query_input(*ty))?;
141
142        let align = layout.layout.align().bytes();
143        let size = layout.layout.size().bytes();
144
145        accum += size;
146        accum = accum.next_multiple_of(Ord::max(4, align));
147
148        // i.e. exceeds 4 32-bit registers
149        if accum > 16 {
150            span = span.or(Some(index));
151        }
152    }
153
154    match span {
155        None => Ok(Ok(())),
156        Some(span) => Ok(Err(span)),
157    }
158}
159
160/// Returns whether the output will fit into the available registers
161fn is_valid_cmse_output<'tcx>(
162    tcx: TyCtxt<'tcx>,
163    fn_sig: ty::PolyFnSig<'tcx>,
164) -> Result<bool, &'tcx LayoutError<'tcx>> {
165    // this type is only used for layout computation, which does not rely on regions
166    let fn_sig = tcx.instantiate_bound_regions_with_erased(fn_sig);
167    let fn_sig = tcx.erase_and_anonymize_regions(fn_sig);
168    let return_type = fn_sig.output();
169
170    // `impl Trait` is already disallowed with `cmse-nonsecure-call`, because that ABI is only
171    // allowed on function pointers, and function pointers cannot contain `impl Trait` in their
172    // signature.
173    //
174    // Here we explicitly disallow `impl Trait` in the `cmse-nonsecure-entry` return type too, to
175    // prevent query cycles when calculating the layout. This ABI is meant to be used with
176    // `#[no_mangle]` or similar, so generics in the type really don't make sense.
177    //
178    // see also https://github.com/rust-lang/rust/issues/147242.
179    if return_type.has_opaque_types() {
180        return Err(tcx.arena.alloc(LayoutError::TooGeneric(return_type)));
181    }
182
183    let typing_env = ty::TypingEnv::fully_monomorphized();
184    let layout = tcx.layout_of(typing_env.as_query_input(return_type))?;
185
186    Ok(is_valid_cmse_output_layout(layout))
187}
188
189/// Returns whether the output will fit into the available registers
190fn is_valid_cmse_output_layout<'tcx>(layout: TyAndLayout<'tcx>) -> bool {
191    let size = layout.layout.size().bytes();
192
193    if size <= 4 {
194        return true;
195    } else if size > 8 {
196        return false;
197    }
198
199    // Accept scalar 64-bit types.
200    let BackendRepr::Scalar(scalar) = layout.layout.backend_repr else {
201        return false;
202    };
203
204    let Scalar::Initialized { value, .. } = scalar else {
205        return false;
206    };
207
208    matches!(value, Primitive::Int(Integer::I64, _) | Primitive::Float(Float::F64))
209}
210
211fn should_emit_generic_error<'tcx>(abi: ExternAbi, layout_err: &'tcx LayoutError<'tcx>) -> bool {
212    use LayoutError::*;
213
214    match layout_err {
215        TooGeneric(ty) => {
216            match abi {
217                ExternAbi::CmseNonSecureCall => {
218                    // prevent double reporting of this error
219                    !ty.is_impl_trait()
220                }
221                ExternAbi::CmseNonSecureEntry => true,
222                _ => bug!("invalid ABI: {abi}"),
223            }
224        }
225        Unknown(..)
226        | SizeOverflow(..)
227        | InvalidSimd { .. }
228        | NormalizationFailure(..)
229        | ReferencesError(..)
230        | Cycle(..) => {
231            false // not our job to report these
232        }
233    }
234}