Skip to content

Commit 50ba821

Browse files
committed
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
1 parent d3dd34a commit 50ba821

File tree

7 files changed

+143
-11
lines changed

7 files changed

+143
-11
lines changed

compiler/rustc_codegen_ssa/messages.ftl

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,18 @@ codegen_ssa_cgu_not_recorded =
1616
1717
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.
1818
19+
codegen_ssa_cmse_call_inputs_stack_spill =
20+
arguments for `C-cmse-nonsecure-call` function too large to pass via registers
21+
.label = this function uses the `C-cmse-nonsecure-call` ABI
22+
.call = but its arguments don't fit in the available registers
23+
.note = functions with the `C-cmse-nonsecure-call` ABI must pass all their arguments via the 4 32-bit available argument registers
24+
25+
codegen_ssa_cmse_call_output_stack_spill =
26+
return value of `C-cmse-nonsecure-call` function too large to pass via registers
27+
.label = this function uses the `C-cmse-nonsecure-call` ABI
28+
.call = but its return value doesn't fit in the available registers
29+
.note = functions with the `C-cmse-nonsecure-call` ABI must pass their result via the available return registers
30+
1931
codegen_ssa_compiler_builtins_cannot_call =
2032
`compiler_builtins` cannot call functions through upstream monomorphizations; encountered invalid call from `{$caller}` to `{$callee}`
2133

compiler/rustc_codegen_ssa/src/errors.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1033,3 +1033,25 @@ pub struct CompilerBuiltinsCannotCall {
10331033
pub caller: String,
10341034
pub callee: String,
10351035
}
1036+
1037+
#[derive(Diagnostic)]
1038+
#[diag(codegen_ssa_cmse_call_inputs_stack_spill, code = E0798)]
1039+
#[note]
1040+
pub struct CmseCallInputsStackSpill {
1041+
#[primary_span]
1042+
#[label(codegen_ssa_call)]
1043+
pub span: Span,
1044+
#[label]
1045+
pub func_span: Span,
1046+
}
1047+
1048+
#[derive(Diagnostic)]
1049+
#[diag(codegen_ssa_cmse_call_output_stack_spill, code = E0798)]
1050+
#[note]
1051+
pub struct CmseCallOutputStackSpill {
1052+
#[primary_span]
1053+
#[label(codegen_ssa_call)]
1054+
pub span: Span,
1055+
#[label]
1056+
pub func_span: Span,
1057+
}

compiler/rustc_codegen_ssa/src/mir/block.rs

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ use super::{CachedLlbb, FunctionCx, LocalRef};
55

66
use crate::base;
77
use crate::common::{self, IntPredicate};
8-
use crate::errors::CompilerBuiltinsCannotCall;
8+
use crate::errors::{
9+
CmseCallInputsStackSpill, CmseCallOutputStackSpill, CompilerBuiltinsCannotCall,
10+
};
911
use crate::meth;
1012
use crate::traits::*;
1113
use crate::MemFlags;
@@ -834,6 +836,58 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
834836
// Create the callee. This is a fn ptr or zero-sized and hence a kind of scalar.
835837
let callee = self.codegen_operand(bx, func);
836838

839+
let fn_sig = callee.layout.ty.fn_sig(bx.tcx()).skip_binder();
840+
841+
if let rustc_target::spec::abi::Abi::CCmseNonSecureCall = fn_sig.abi {
842+
let mut accum = 0u64;
843+
844+
for arg_def in fn_sig.inputs().iter() {
845+
let layout = bx.layout_of(*arg_def);
846+
847+
let align = layout.layout.align().abi.bytes();
848+
let size = layout.layout.size().bytes();
849+
850+
accum += size;
851+
accum = accum.next_multiple_of(Ord::max(4, align));
852+
}
853+
854+
// the available argument space is 16 bytes (4 32-bit registers) in total
855+
let available_space = 16;
856+
857+
if accum > available_space {
858+
let err = CmseCallInputsStackSpill { span, func_span: func.span(self.mir) };
859+
bx.tcx().dcx().emit_err(err);
860+
}
861+
862+
let mut ret_layout = bx.layout_of(fn_sig.output());
863+
864+
// unwrap any `repr(transparent)` wrappers
865+
loop {
866+
if ret_layout.is_transparent::<Bx>() {
867+
match ret_layout.non_1zst_field(bx) {
868+
None => break,
869+
Some((_, layout)) => ret_layout = layout,
870+
}
871+
} else {
872+
break;
873+
}
874+
}
875+
876+
let valid_2register_return_types =
877+
[bx.tcx().types.i64, bx.tcx().types.u64, bx.tcx().types.f64];
878+
879+
// A Composite Type larger than 4 bytes is stored in memory at an address
880+
// passed as an extra argument when the function was called. That is not allowed
881+
// for cmse_nonsecure_entry functions.
882+
let is_valid_output = ret_layout.layout.size().bytes() <= 4
883+
|| valid_2register_return_types.contains(&ret_layout.ty);
884+
885+
if !is_valid_output {
886+
let err = CmseCallOutputStackSpill { span, func_span: func.span(self.mir) };
887+
bx.tcx().dcx().emit_err(err);
888+
}
889+
}
890+
837891
let (instance, mut llfn) = match *callee.layout.ty.kind() {
838892
ty::FnDef(def_id, args) => (
839893
Some(
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
Functions marked as `C-cmse-nonsecure-call` place restrictions on their
2+
inputs and outputs.
3+
4+
- inputs must fit in the 4 available 32-bit argument registers. Alignment
5+
is relevant.
6+
- outputs must either fit in 4 bytes, or be a foundational type of
7+
size 8 (`i64`, `u64`, `f64`).
8+
9+
For more information,
10+
see [arm's aapcs32](https://github.com/ARM-software/abi-aa/releases).
11+
12+
Erroneous code example:
13+
14+
```compile_fail,E0798
15+
#![feature(abi_c_cmse_nonsecure_call)]
16+
17+
fn test(
18+
f: extern "C-cmse-nonsecure-call" fn(u32, u32, u32, u32, u32) -> u32,
19+
) -> u32 {
20+
f(1, 2, 3, 4, 5)
21+
}
22+
```
23+
24+
Arguments' alignment is respected. In the example below, padding is inserted
25+
so that the `u64` argument is passed in registers r2 and r3. There is then no
26+
room left for the final `f32` argument
27+
28+
```compile_fail,E0798
29+
#![feature(abi_c_cmse_nonsecure_call)]
30+
31+
fn test(
32+
f: extern "C-cmse-nonsecure-call" fn(u32, u64, f32) -> u32,
33+
) -> u32 {
34+
f(1, 2, 3.0)
35+
}
36+
```

compiler/rustc_error_codes/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,7 @@ E0794: 0794,
536536
E0795: 0795,
537537
E0796: 0796,
538538
E0797: 0797,
539+
E0798: 0798,
539540
);
540541
)
541542
}

tests/ui/cmse-nonsecure/cmse-nonsecure-call/params-on-stack.rs

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
//@ needs-llvm-components: arm
44
#![feature(abi_c_cmse_nonsecure_call, no_core, lang_items, intrinsics)]
55
#![no_core]
6-
#[lang="sized"]
7-
pub trait Sized { }
8-
#[lang="copy"]
9-
pub trait Copy { }
6+
#[lang = "sized"]
7+
pub trait Sized {}
8+
#[lang = "copy"]
9+
pub trait Copy {}
1010
impl Copy for u32 {}
1111

1212
extern "rust-intrinsic" {
@@ -16,12 +16,9 @@ extern "rust-intrinsic" {
1616
#[no_mangle]
1717
pub fn test(a: u32, b: u32, c: u32, d: u32, e: u32) -> u32 {
1818
let non_secure_function = unsafe {
19-
transmute::<
20-
usize,
21-
extern "C-cmse-nonsecure-call" fn(u32, u32, u32, u32, u32) -> u32>
22-
(
19+
transmute::<usize, extern "C-cmse-nonsecure-call" fn(u32, u32, u32, u32, u32) -> u32>(
2320
0x10000004,
2421
)
2522
};
26-
non_secure_function(a, b, c, d, e)
23+
non_secure_function(a, b, c, d, e) //~ ERROR [E0798]
2724
}
Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
1-
error: <unknown>:0:0: in function test i32 (i32, i32, i32, i32, i32): call to non-secure function would require passing arguments on stack
1+
error[E0798]: arguments for `C-cmse-nonsecure-call` function too large to pass via registers
2+
--> $DIR/params-on-stack.rs:23:5
3+
|
4+
LL | let non_secure_function = unsafe {
5+
| ------------------- this function uses the `C-cmse-nonsecure-call` ABI
6+
...
7+
LL | non_secure_function(a, b, c, d, e)
8+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ but its arguments don't fit in the available registers
9+
|
10+
= note: functions with the `C-cmse-nonsecure-call` ABI must pass all their arguments via the 4 32-bit available argument registers
211

312
error: aborting due to 1 previous error
413

14+
For more information about this error, try `rustc --explain E0798`.

0 commit comments

Comments
 (0)