Skip to main content

rustc_hir_analysis/hir_ty_lowering/
cmse.rs

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