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::bug;
5use rustc_middle::ty::layout::LayoutError;
6use rustc_middle::ty::{self, TyCtxt};
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::CCmseNonSecureCall => {
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                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                    tcx.dcx(),
39                    span,
40                    E0781,
41                    "the `\"C-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) = bare_fn_ty.param_names[index] {
53                        ident.span.to(bare_fn_ty.decl.inputs[index].span)
54                    } else {
55                        bare_fn_ty.decl.inputs[index].span
56                    }
57                    .to(bare_fn_ty.decl.inputs.last().unwrap().span);
58                    let plural = bare_fn_ty.param_names.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: *bare_fn_span });
64                    }
65                }
66            }
67
68            match is_valid_cmse_output(tcx, fn_sig) {
69                Ok(true) => {}
70                Ok(false) => {
71                    let span = bare_fn_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: *bare_fn_span });
77                    }
78                }
79            };
80        }
81        ExternAbi::CCmseNonSecureEntry => {
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            match is_valid_cmse_inputs(tcx, fn_sig) {
89                Ok(Ok(())) => {}
90                Ok(Err(index)) => {
91                    // fn f(x: u32, y: u32, z: u32, w: u16, q: u16) -> u32,
92                    //                                      ^^^^^^
93                    let span = decl.inputs[index].span.to(decl.inputs.last().unwrap().span);
94                    let plural = decl.inputs.len() - index != 1;
95                    dcx.emit_err(errors::CmseInputsStackSpill { span, plural, abi });
96                }
97                Err(layout_err) => {
98                    if should_emit_generic_error(abi, layout_err) {
99                        dcx.emit_err(errors::CmseEntryGeneric { span: *fn_sig_span });
100                    }
101                }
102            }
103
104            match is_valid_cmse_output(tcx, fn_sig) {
105                Ok(true) => {}
106                Ok(false) => {
107                    let span = decl.output.span();
108                    dcx.emit_err(errors::CmseOutputStackSpill { span, abi });
109                }
110                Err(layout_err) => {
111                    if should_emit_generic_error(abi, layout_err) {
112                        dcx.emit_err(errors::CmseEntryGeneric { span: *fn_sig_span });
113                    }
114                }
115            };
116        }
117        _ => (),
118    }
119}
120
121/// Returns whether the inputs will fit into the available registers
122fn is_valid_cmse_inputs<'tcx>(
123    tcx: TyCtxt<'tcx>,
124    fn_sig: ty::PolyFnSig<'tcx>,
125) -> Result<Result<(), usize>, &'tcx LayoutError<'tcx>> {
126    let mut span = None;
127    let mut accum = 0u64;
128
129    // this type is only used for layout computation, which does not rely on regions
130    let fn_sig = tcx.instantiate_bound_regions_with_erased(fn_sig);
131
132    for (index, ty) in fn_sig.inputs().iter().enumerate() {
133        let layout = tcx.layout_of(ty::TypingEnv::fully_monomorphized().as_query_input(*ty))?;
134
135        let align = layout.layout.align().abi.bytes();
136        let size = layout.layout.size().bytes();
137
138        accum += size;
139        accum = accum.next_multiple_of(Ord::max(4, align));
140
141        // i.e. exceeds 4 32-bit registers
142        if accum > 16 {
143            span = span.or(Some(index));
144        }
145    }
146
147    match span {
148        None => Ok(Ok(())),
149        Some(span) => Ok(Err(span)),
150    }
151}
152
153/// Returns whether the output will fit into the available registers
154fn is_valid_cmse_output<'tcx>(
155    tcx: TyCtxt<'tcx>,
156    fn_sig: ty::PolyFnSig<'tcx>,
157) -> Result<bool, &'tcx LayoutError<'tcx>> {
158    // this type is only used for layout computation, which does not rely on regions
159    let fn_sig = tcx.instantiate_bound_regions_with_erased(fn_sig);
160
161    let typing_env = ty::TypingEnv::fully_monomorphized();
162
163    let mut ret_ty = fn_sig.output();
164    let layout = tcx.layout_of(typing_env.as_query_input(ret_ty))?;
165    let size = layout.layout.size().bytes();
166
167    if size <= 4 {
168        return Ok(true);
169    } else if size > 8 {
170        return Ok(false);
171    }
172
173    // next we need to peel any repr(transparent) layers off
174    'outer: loop {
175        let ty::Adt(adt_def, args) = ret_ty.kind() else {
176            break;
177        };
178
179        if !adt_def.repr().transparent() {
180            break;
181        }
182
183        // the first field with non-trivial size and alignment must be the data
184        for variant_def in adt_def.variants() {
185            for field_def in variant_def.fields.iter() {
186                let ty = field_def.ty(tcx, args);
187                let layout = tcx.layout_of(typing_env.as_query_input(ty))?;
188
189                if !layout.layout.is_1zst() {
190                    ret_ty = ty;
191                    continue 'outer;
192                }
193            }
194        }
195    }
196
197    Ok(ret_ty == tcx.types.i64 || ret_ty == tcx.types.u64 || ret_ty == tcx.types.f64)
198}
199
200fn should_emit_generic_error<'tcx>(abi: ExternAbi, layout_err: &'tcx LayoutError<'tcx>) -> bool {
201    use LayoutError::*;
202
203    match layout_err {
204        TooGeneric(ty) => {
205            match abi {
206                ExternAbi::CCmseNonSecureCall => {
207                    // prevent double reporting of this error
208                    !ty.is_impl_trait()
209                }
210                ExternAbi::CCmseNonSecureEntry => true,
211                _ => bug!("invalid ABI: {abi}"),
212            }
213        }
214        Unknown(..)
215        | SizeOverflow(..)
216        | NormalizationFailure(..)
217        | ReferencesError(..)
218        | Cycle(..) => {
219            false // not our job to report these
220        }
221    }
222}