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