From c7ff46c971ea7c17b67b2b619fd094c4df981863 Mon Sep 17 00:00:00 2001 From: Folkert Date: Tue, 16 Jul 2024 18:39:30 +0200 Subject: [PATCH] move cmse ABI validation into its own module Co-authored-by: Tamme Dittrich --- compiler/rustc_codegen_ssa/src/errors.rs | 8 +-- compiler/rustc_codegen_ssa/src/mir/block.rs | 60 ++--------------- compiler/rustc_codegen_ssa/src/mir/cmse.rs | 72 +++++++++++++++++++++ compiler/rustc_codegen_ssa/src/mir/mod.rs | 1 + 4 files changed, 82 insertions(+), 59 deletions(-) create mode 100644 compiler/rustc_codegen_ssa/src/mir/cmse.rs diff --git a/compiler/rustc_codegen_ssa/src/errors.rs b/compiler/rustc_codegen_ssa/src/errors.rs index 13a2dce3e69..af7835633cf 100644 --- a/compiler/rustc_codegen_ssa/src/errors.rs +++ b/compiler/rustc_codegen_ssa/src/errors.rs @@ -1040,9 +1040,9 @@ pub struct CompilerBuiltinsCannotCall { pub struct CmseCallInputsStackSpill { #[primary_span] #[label(codegen_ssa_call)] - pub span: Span, + pub call_site_span: Span, #[label] - pub func_span: Span, + pub function_definition_span: Span, } #[derive(Diagnostic)] @@ -1051,7 +1051,7 @@ pub struct CmseCallInputsStackSpill { pub struct CmseCallOutputStackSpill { #[primary_span] #[label(codegen_ssa_call)] - pub span: Span, + pub call_site_span: Span, #[label] - pub func_span: Span, + pub function_definition_span: Span, } diff --git a/compiler/rustc_codegen_ssa/src/mir/block.rs b/compiler/rustc_codegen_ssa/src/mir/block.rs index 8011604d576..24d3b8ad264 100644 --- a/compiler/rustc_codegen_ssa/src/mir/block.rs +++ b/compiler/rustc_codegen_ssa/src/mir/block.rs @@ -5,10 +5,9 @@ use super::{CachedLlbb, FunctionCx, LocalRef}; use crate::base; use crate::common::{self, IntPredicate}; -use crate::errors::{ - CmseCallInputsStackSpill, CmseCallOutputStackSpill, CompilerBuiltinsCannotCall, -}; +use crate::errors::CompilerBuiltinsCannotCall; use crate::meth; +use crate::mir::cmse; use crate::traits::*; use crate::MemFlags; @@ -836,58 +835,6 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { // Create the callee. This is a fn ptr or zero-sized and hence a kind of scalar. let callee = self.codegen_operand(bx, func); - let fn_sig = callee.layout.ty.fn_sig(bx.tcx()).skip_binder(); - - if let rustc_target::spec::abi::Abi::CCmseNonSecureCall = fn_sig.abi { - let mut accum = 0u64; - - for arg_def in fn_sig.inputs().iter() { - let layout = bx.layout_of(*arg_def); - - let align = layout.layout.align().abi.bytes(); - let size = layout.layout.size().bytes(); - - accum += size; - accum = accum.next_multiple_of(Ord::max(4, align)); - } - - // the available argument space is 16 bytes (4 32-bit registers) in total - let available_space = 16; - - if accum > available_space { - let err = CmseCallInputsStackSpill { span, func_span: func.span(self.mir) }; - bx.tcx().dcx().emit_err(err); - } - - let mut ret_layout = bx.layout_of(fn_sig.output()); - - // unwrap any `repr(transparent)` wrappers - loop { - if ret_layout.is_transparent::() { - match ret_layout.non_1zst_field(bx) { - None => break, - Some((_, layout)) => ret_layout = layout, - } - } else { - break; - } - } - - let valid_2register_return_types = - [bx.tcx().types.i64, bx.tcx().types.u64, bx.tcx().types.f64]; - - // A Composite Type larger than 4 bytes is stored in memory at an address - // passed as an extra argument when the function was called. That is not allowed - // for cmse_nonsecure_entry functions. - let is_valid_output = ret_layout.layout.size().bytes() <= 4 - || valid_2register_return_types.contains(&ret_layout.ty); - - if !is_valid_output { - let err = CmseCallOutputStackSpill { span, func_span: func.span(self.mir) }; - bx.tcx().dcx().emit_err(err); - } - } - let (instance, mut llfn) = match *callee.layout.ty.kind() { ty::FnDef(def_id, args) => ( Some( @@ -923,6 +870,9 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { let sig = callee.layout.ty.fn_sig(bx.tcx()); let abi = sig.abi(); + // emit errors if cmse ABI conditions are violated + cmse::validate_cmse_abi(bx, &sig.skip_binder(), span, func.span(self.mir)); + let extra_args = &args[sig.inputs().skip_binder().len()..]; let extra_args = bx.tcx().mk_type_list_from_iter(extra_args.iter().map(|op_arg| { let op_ty = op_arg.node.ty(self.mir, bx.tcx()); diff --git a/compiler/rustc_codegen_ssa/src/mir/cmse.rs b/compiler/rustc_codegen_ssa/src/mir/cmse.rs new file mode 100644 index 00000000000..d3583d79c00 --- /dev/null +++ b/compiler/rustc_codegen_ssa/src/mir/cmse.rs @@ -0,0 +1,72 @@ +use rustc_middle::ty::FnSig; +use rustc_span::Span; + +use crate::errors::{CmseCallInputsStackSpill, CmseCallOutputStackSpill}; +use crate::traits::BuilderMethods; + +/// Check conditions on inputs and outputs that the cmse ABIs impose: arguments and results MUST be +/// returned via registers (i.e. MUST NOT spill to the stack). LLVM will also validate these +/// conditions, but by checking them here rustc can emit nicer error messages. +pub fn validate_cmse_abi<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( + bx: &Bx, + fn_sig: &FnSig<'tcx>, + call_site_span: Span, + function_definition_span: Span, +) { + if let rustc_target::spec::abi::Abi::CCmseNonSecureCall = fn_sig.abi { + if !has_valid_inputs(bx, fn_sig) { + let err = CmseCallInputsStackSpill { call_site_span, function_definition_span }; + bx.tcx().dcx().emit_err(err); + } + + if !has_valid_output(bx, fn_sig) { + let err = CmseCallOutputStackSpill { call_site_span, function_definition_span }; + bx.tcx().dcx().emit_err(err); + } + } +} + +/// Returns whether the inputs will fit into the available registers +fn has_valid_inputs<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(bx: &Bx, fn_sig: &FnSig<'tcx>) -> bool { + let mut accum = 0u64; + + for arg_def in fn_sig.inputs().iter() { + let layout = bx.layout_of(*arg_def); + + let align = layout.layout.align().abi.bytes(); + let size = layout.layout.size().bytes(); + + accum += size; + accum = accum.next_multiple_of(Ord::max(4, align)); + } + + // the available argument space is 16 bytes (4 32-bit registers) in total + let available_space = 16; + + accum <= available_space +} + +/// Returns whether the output will fit into the available registers +fn has_valid_output<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(bx: &Bx, fn_sig: &FnSig<'tcx>) -> bool { + let mut ret_layout = bx.layout_of(fn_sig.output()); + + // unwrap any `repr(transparent)` wrappers + loop { + if ret_layout.is_transparent::() { + match ret_layout.non_1zst_field(bx) { + None => break, + Some((_, layout)) => ret_layout = layout, + } + } else { + break; + } + } + + // Fundamental types of size 8 can be passed via registers according to the ABI + let valid_2register_return_types = [bx.tcx().types.i64, bx.tcx().types.u64, bx.tcx().types.f64]; + + // A Composite Type larger than 4 bytes is stored in memory at an address + // passed as an extra argument when the function was called. That is not allowed + // for cmse_nonsecure_entry functions. + ret_layout.layout.size().bytes() <= 4 || valid_2register_return_types.contains(&ret_layout.ty) +} diff --git a/compiler/rustc_codegen_ssa/src/mir/mod.rs b/compiler/rustc_codegen_ssa/src/mir/mod.rs index 61f57c9030a..ec28def8b83 100644 --- a/compiler/rustc_codegen_ssa/src/mir/mod.rs +++ b/compiler/rustc_codegen_ssa/src/mir/mod.rs @@ -16,6 +16,7 @@ use std::iter; mod analyze; mod block; +mod cmse; pub mod constant; pub mod coverageinfo; pub mod debuginfo;