From 50ba821e12f51903aba6902b2b404edbc94011d2 Mon Sep 17 00:00:00 2001 From: Folkert Date: Tue, 16 Jul 2024 15:57:00 +0200 Subject: [PATCH] add rust error message for CMSE stack spill when the `C-cmse-nonsecure-call` ABI is used, arguments and return values must be passed via registers. Failing to do so (i.e. spilling to the stack) causes an LLVM error down the line, but now rustc will properly emit an error a bit earlier in the chain --- compiler/rustc_codegen_ssa/messages.ftl | 12 ++++ compiler/rustc_codegen_ssa/src/errors.rs | 22 ++++++++ compiler/rustc_codegen_ssa/src/mir/block.rs | 56 ++++++++++++++++++- .../src/error_codes/E0798.md | 36 ++++++++++++ compiler/rustc_error_codes/src/lib.rs | 1 + .../cmse-nonsecure-call/params-on-stack.rs | 15 ++--- .../params-on-stack.stderr | 12 +++- 7 files changed, 143 insertions(+), 11 deletions(-) create mode 100644 compiler/rustc_error_codes/src/error_codes/E0798.md diff --git a/compiler/rustc_codegen_ssa/messages.ftl b/compiler/rustc_codegen_ssa/messages.ftl index 000fe2e3ce0..0aee5e16a53 100644 --- a/compiler/rustc_codegen_ssa/messages.ftl +++ b/compiler/rustc_codegen_ssa/messages.ftl @@ -16,6 +16,18 @@ codegen_ssa_cgu_not_recorded = codegen_ssa_check_installed_visual_studio = please ensure that Visual Studio 2017 or later, or Build Tools for Visual Studio were installed with the Visual C++ option. +codegen_ssa_cmse_call_inputs_stack_spill = + arguments for `C-cmse-nonsecure-call` function too large to pass via registers + .label = this function uses the `C-cmse-nonsecure-call` ABI + .call = but its arguments don't fit in the available registers + .note = functions with the `C-cmse-nonsecure-call` ABI must pass all their arguments via the 4 32-bit available argument registers + +codegen_ssa_cmse_call_output_stack_spill = + return value of `C-cmse-nonsecure-call` function too large to pass via registers + .label = this function uses the `C-cmse-nonsecure-call` ABI + .call = but its return value doesn't fit in the available registers + .note = functions with the `C-cmse-nonsecure-call` ABI must pass their result via the available return registers + codegen_ssa_compiler_builtins_cannot_call = `compiler_builtins` cannot call functions through upstream monomorphizations; encountered invalid call from `{$caller}` to `{$callee}` diff --git a/compiler/rustc_codegen_ssa/src/errors.rs b/compiler/rustc_codegen_ssa/src/errors.rs index e9d31db9254..13a2dce3e69 100644 --- a/compiler/rustc_codegen_ssa/src/errors.rs +++ b/compiler/rustc_codegen_ssa/src/errors.rs @@ -1033,3 +1033,25 @@ pub struct CompilerBuiltinsCannotCall { pub caller: String, pub callee: String, } + +#[derive(Diagnostic)] +#[diag(codegen_ssa_cmse_call_inputs_stack_spill, code = E0798)] +#[note] +pub struct CmseCallInputsStackSpill { + #[primary_span] + #[label(codegen_ssa_call)] + pub span: Span, + #[label] + pub func_span: Span, +} + +#[derive(Diagnostic)] +#[diag(codegen_ssa_cmse_call_output_stack_spill, code = E0798)] +#[note] +pub struct CmseCallOutputStackSpill { + #[primary_span] + #[label(codegen_ssa_call)] + pub span: Span, + #[label] + pub func_span: Span, +} diff --git a/compiler/rustc_codegen_ssa/src/mir/block.rs b/compiler/rustc_codegen_ssa/src/mir/block.rs index 6a5525dc2b3..8011604d576 100644 --- a/compiler/rustc_codegen_ssa/src/mir/block.rs +++ b/compiler/rustc_codegen_ssa/src/mir/block.rs @@ -5,7 +5,9 @@ use super::{CachedLlbb, FunctionCx, LocalRef}; use crate::base; use crate::common::{self, IntPredicate}; -use crate::errors::CompilerBuiltinsCannotCall; +use crate::errors::{ + CmseCallInputsStackSpill, CmseCallOutputStackSpill, CompilerBuiltinsCannotCall, +}; use crate::meth; use crate::traits::*; use crate::MemFlags; @@ -834,6 +836,58 @@ 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( diff --git a/compiler/rustc_error_codes/src/error_codes/E0798.md b/compiler/rustc_error_codes/src/error_codes/E0798.md new file mode 100644 index 00000000000..79ed041004e --- /dev/null +++ b/compiler/rustc_error_codes/src/error_codes/E0798.md @@ -0,0 +1,36 @@ +Functions marked as `C-cmse-nonsecure-call` place restrictions on their +inputs and outputs. + +- inputs must fit in the 4 available 32-bit argument registers. Alignment +is relevant. +- outputs must either fit in 4 bytes, or be a foundational type of +size 8 (`i64`, `u64`, `f64`). + +For more information, +see [arm's aapcs32](https://github.com/ARM-software/abi-aa/releases). + +Erroneous code example: + +```compile_fail,E0798 +#![feature(abi_c_cmse_nonsecure_call)] + +fn test( + f: extern "C-cmse-nonsecure-call" fn(u32, u32, u32, u32, u32) -> u32, +) -> u32 { + f(1, 2, 3, 4, 5) +} +``` + +Arguments' alignment is respected. In the example below, padding is inserted +so that the `u64` argument is passed in registers r2 and r3. There is then no +room left for the final `f32` argument + +```compile_fail,E0798 +#![feature(abi_c_cmse_nonsecure_call)] + +fn test( + f: extern "C-cmse-nonsecure-call" fn(u32, u64, f32) -> u32, +) -> u32 { + f(1, 2, 3.0) +} +``` diff --git a/compiler/rustc_error_codes/src/lib.rs b/compiler/rustc_error_codes/src/lib.rs index d13d5e1bca2..2a7bc2501c0 100644 --- a/compiler/rustc_error_codes/src/lib.rs +++ b/compiler/rustc_error_codes/src/lib.rs @@ -536,6 +536,7 @@ E0794: 0794, E0795: 0795, E0796: 0796, E0797: 0797, +E0798: 0798, ); ) } diff --git a/tests/ui/cmse-nonsecure/cmse-nonsecure-call/params-on-stack.rs b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/params-on-stack.rs index c225a26c065..f13e81d0060 100644 --- a/tests/ui/cmse-nonsecure/cmse-nonsecure-call/params-on-stack.rs +++ b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/params-on-stack.rs @@ -3,10 +3,10 @@ //@ needs-llvm-components: arm #![feature(abi_c_cmse_nonsecure_call, no_core, lang_items, intrinsics)] #![no_core] -#[lang="sized"] -pub trait Sized { } -#[lang="copy"] -pub trait Copy { } +#[lang = "sized"] +pub trait Sized {} +#[lang = "copy"] +pub trait Copy {} impl Copy for u32 {} extern "rust-intrinsic" { @@ -16,12 +16,9 @@ extern "rust-intrinsic" { #[no_mangle] pub fn test(a: u32, b: u32, c: u32, d: u32, e: u32) -> u32 { let non_secure_function = unsafe { - transmute::< - usize, - extern "C-cmse-nonsecure-call" fn(u32, u32, u32, u32, u32) -> u32> - ( + transmute:: u32>( 0x10000004, ) }; - non_secure_function(a, b, c, d, e) + non_secure_function(a, b, c, d, e) //~ ERROR [E0798] } diff --git a/tests/ui/cmse-nonsecure/cmse-nonsecure-call/params-on-stack.stderr b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/params-on-stack.stderr index a8aced2483e..ee4effa56d4 100644 --- a/tests/ui/cmse-nonsecure/cmse-nonsecure-call/params-on-stack.stderr +++ b/tests/ui/cmse-nonsecure/cmse-nonsecure-call/params-on-stack.stderr @@ -1,4 +1,14 @@ -error: :0:0: in function test i32 (i32, i32, i32, i32, i32): call to non-secure function would require passing arguments on stack +error[E0798]: arguments for `C-cmse-nonsecure-call` function too large to pass via registers + --> $DIR/params-on-stack.rs:23:5 + | +LL | let non_secure_function = unsafe { + | ------------------- this function uses the `C-cmse-nonsecure-call` ABI +... +LL | non_secure_function(a, b, c, d, e) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ but its arguments don't fit in the available registers + | + = note: functions with the `C-cmse-nonsecure-call` ABI must pass all their arguments via the 4 32-bit available argument registers error: aborting due to 1 previous error +For more information about this error, try `rustc --explain E0798`.